中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

【譯】Go 中如何進(jìn)行單元測試

2018-07-20    來源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢上線!快速搭建集群,上萬Linux鏡像隨意使用

在寫《Go語言標(biāo)準(zhǔn)庫》的第九章 —— 測試 時,看到了此文,講解挺細(xì)致,于是翻譯為中文,作為學(xué)習(xí)《Go語言標(biāo)準(zhǔn)庫》的第九章的補(bǔ)充材料。

如果你花過一些時間學(xué)習(xí)如何編程,你很可能見過許多地方提過測試。似乎每個人都在談?wù)摐y試,似乎都同意你應(yīng)該進(jìn)行測試,但這到底需要什么呢?

在這篇文章中,我將嘗試回答這個問題,首先解釋什么是測試,然后我會用 Go 去深入實(shí)際編寫測試。在編寫測試時,我將通過編寫自定義 main 包,使用 testing 包以及更復(fù)雜的功能(如自定義 setup 和 teardown)以及創(chuàng)建可以用作測試用例的示例代碼來覆蓋所有內(nèi)容。

什么是測試?

讓我們從最簡單的問題開始 — 什么是測試?

簡單地說,測試是一個可重復(fù)的過程,它驗證某個東西是否按預(yù)期工作。雖然您通常會聽到在軟件世界中的測試,但它們并不限于軟件。

如果您購買和轉(zhuǎn)售二手電視機(jī),您可能會有一個測試過程,包括將電視插入筆記本電腦的 HDMI 端口,并驗證顯示器和音頻是否在電視上工作。這也是測試。

雖然測試似乎需要一些復(fù)雜和自動化的過程,但事實(shí)是測試可以從手動鍵入 www.yoursite.com 到您的瀏覽器,以驗證您的部署是否有效,或者它們可能與 Google’s DiRT 一樣復(fù)雜 — 該公司試圖測試他們的系統(tǒng)如何在僵尸啟示的情況下自動響應(yīng)。測試只是一種幫助確定某件事情在特定情況下是否按預(yù)期工作的方法。

在前面的電視示例中,您的測試用于確保在插入標(biāo)準(zhǔn)輸入時電視工作正常;而在軟件世界中, 您的測試可能用于確定某個函數(shù)是否按您的預(yù)期運(yùn)行。

編寫一個程序測試

雖然測試不需要,但在編程世界中,測試通常通過編寫更多代碼來自動化。它們的目的與任何手動執(zhí)行的測試相同,但由于它們是用代碼編寫的,所以這些測試具有更多的優(yōu)勢,可以更快地執(zhí)行,并且您可以與其他開發(fā)人員共享它們。

例如,假設(shè)我們需要編寫一個函數(shù) Sum 來計算 slice 中提供的所有整數(shù)的和,并返回它,我們想出了下面的代碼。

func Sum(numbers []int) int {  
  sum := 0
  // 這個 bug 是故意的
  for n := range numbers {
    sum += n
  }
  return sum
}

現(xiàn)在,假設(shè)您想為這個函數(shù)編寫一些測試,以確保它按您的預(yù)期運(yùn)行。如果您對測試工具不熟悉 (如果您正在閱讀這篇文章,我假設(shè)您確實(shí)不熟悉),一個方法是創(chuàng)建一個使用 Sum() 函數(shù)的 main 包,如果結(jié)果不是我們所期望的,則顯示一條錯誤消息。

package main

import (  
  "fmt"

  "calhoun.io/testing101"
)

func main() {  
  testSum([]int{2, 2, 2, 4}, 10)
  testSum([]int{-1, -2, -3, -4, 5}, -5)
}

func testSum(numbers []int, expected int) {  
  sum := testing101.Sum(numbers)
  if sum != expected {
    message := fmt.Sprintf("Expected the sum of %v to be %d but instead got %d!", numbers, expected, sum)
    panic(message)
  }
}

注意:這里假定您的 Sum() 函數(shù)在一個名為 testing101 的包中。如果您在本地自己編寫代碼,記得導(dǎo)入正確的包。

如果我們運(yùn)行此代碼,會注意到 Sum() 函數(shù)實(shí)際上沒有按預(yù)期的方式工作,我們沒有得到期望的值:10,而是得到 6。進(jìn)一步排查后,我們可能會意識到,我們使用的是在切片中的索引,而不是切片中每個項的實(shí)際值。為了解決這個問題,我們需要更新該行:

for n := range numbers {

改為

for _, n := range numbers {

在進(jìn)行更改后, 我們可以重新運(yùn)行 main() 函數(shù),這次不會得到任何輸出,說明測試用例失敗。這就是測試的威力 — 在幾分鐘內(nèi),我們就我們的代碼是否正常工作提供了反饋意見。我們可以快速驗證代碼是否如我們修改的方式工作。另外,如果我們將此代碼發(fā)送給其他開發(fā)人員,他們還可以繼續(xù)運(yùn)行相同的測試,并驗證它們沒有破壞您的代碼。

通過 go test 進(jìn)行測試

雖然上面所示的方法可能適用于小型項目,但要編寫一個 main 包來測試所有我們想要檢測的內(nèi)容會變得非常麻煩。幸運(yùn)的是,在 testing 包,Go 為我們提供一些很好的功能,我們可以在不需要太多學(xué)習(xí)的情況下使用它們。

若要在 Go 中開始使用測試,首先需要定義要測試的包。如果還沒有,請創(chuàng)建一個名為 testing101 的包,并創(chuàng)建文件 sum.go,添加上下面的代碼:(代碼跟上面的一樣)

package testing101

func Sum(numbers []int) int {  
  sum := 0
  // 這個 bug 是故意的
  for _, n := range numbers {
    sum += n
  }
  return sum
}

接下來在同一個包中,創(chuàng)建一個名為 sum_test.go 的文件,并將下面的代碼添加到其中。

package testing101

import (  
  "fmt"
  "testing"
)

func TestSum(t *testing.T) {  
  numbers := []int{1, 2, 3, 4, 5}
  expected := 15
  actual := Sum(numbers)

  if actual != expected {
    t.Errorf("Expected the sum of %v to be %d but instead got %d!", numbers, expected, actual)
  }
}

現(xiàn)在我們要運(yùn)行我們的測試,所以在終端中切換到 testing101 包所在目錄,并使用下面的命令運(yùn)行測試。

go test -v

你應(yīng)該看到像這樣的輸出:

=== RUN TestSum

— PASS: TestSum (0.00s)

PASS

ok calhoun.io/testing101 0.005s

恭喜!您剛剛使用 Go 內(nèi)置的 testing 編寫了第一個測試,F(xiàn)在,讓我們深入了解實(shí)際發(fā)生的事情。

首先,是我們的文件名。Go 要求所有的測試都在以 _test.go 結(jié)尾的文件中。這使得我們在檢查另一個 package 包的源代碼時,確定哪些文件是測試和哪些文件實(shí)現(xiàn)功能非常容易。

在看了文件名之后,我們可以直接跳轉(zhuǎn)到代碼中,將測試包導(dǎo)入。它為我們提供了一些類型 (如testing.T) ,這些類型提供常見功能,比如在測試失敗時設(shè)置錯誤消息。

接下來,是函數(shù) TestSum()。所有的測試都應(yīng)該以 func TestXxx(*testing.T) 的格式來編寫。其中 Xxx 可以是任何字符或數(shù)字,而第一個字符需要是大寫字符或數(shù)字。(譯注:一般,Xxx 就是被測試的函數(shù)名)

最后,如上所述,我們使用了 TestSum 函數(shù)中的參數(shù) *tesing.T 。如果我們沒有得到預(yù)期的結(jié)果,我們使用它來設(shè)置一個錯誤,當(dāng)我們運(yùn)行測試時,該錯誤將顯示在終端上。若要查看此操作,請將測試代碼中的 expected 更新為 18,而不更新 numbers 變量,然后使用 go test -v 運(yùn)行測試。您應(yīng)該會看到顯示如下所示錯誤信息的輸出:

=== RUN TestSum

— FAIL: TestSum (0.00s)

sum_test.go:14: Expected the sum of [1 2 3 4 5] to be 18 but instead got 15!

FAIL

exit status 1

FAIL calhoun.io/testing101 0.005s

在本節(jié)的所有內(nèi)容中,您應(yīng)該能夠開始對所有代碼進(jìn)行一些基本測試,但如果需要為同一函數(shù)添加更多測試用例,或者需要構(gòu)造自己的類型來測試代碼,會發(fā)生什么情況?

每個函數(shù)多個測試用例

在上面的 case 中,我們的 Sum() 函數(shù)的代碼非常簡單,但是當(dāng)您編寫自己的代碼時,您可能會發(fā)現(xiàn)自己想要為每個函數(shù)添加更多的測試用例而不僅僅是一個。例如,我們可能希望驗證 Sum() 是否能正確處理負(fù)數(shù)。

在 Go 中運(yùn)行多個測試用例有幾種選擇。一個選擇是簡單地在我們的 sum_test.go 中創(chuàng)建另一個函數(shù)。例如,我們可以添加函數(shù) TestSumWithNegatives() 。這是迄今為止最簡單的方法,但它可能導(dǎo)致某些代碼重復(fù),而且我們的測試輸出中沒有很好的嵌套測試用例。

另一種選擇,不是創(chuàng)建多個 TestXxx() 函數(shù),而是使用 testing.T 的 Run 方法,它允許我們傳遞一個要運(yùn)行的子測試的名稱,以及一個用于測試的函數(shù)。打開 sum_test.go 并更新為如下代碼:(參考: 【譯】子測試和子基準(zhǔn)測試的使用 )

package testing101

import (  
  "fmt"
  "testing"
)

func TestSum(t *testing.T) {  
  t.Run("[1,2,3,4,5]", testSumFunc([]int{1, 2, 3, 4, 5}, 15))
  t.Run("[1,2,3,4,-5]", testSumFunc([]int{1, 2, 3, 4, -5}, 5))
}

func testSumFunc(numbers []int, expected int) func(*testing.T) {  
  return func(t *testing.T) {
    actual := Sum(numbers)
    if actual != expected {
      t.Error(fmt.Sprintf("Expected the sum of %v to be %d but instead got %d!", numbers, expected, actual))
    }
  }
}

此示例使用了一個閉包,它是一個函數(shù),它使用了沒有直接傳遞給它的變量(外部函數(shù)的變量)。這對于創(chuàng)建一個只接受 testing.T 變量的函數(shù)很有用,而且也可以訪問我們要為每個測試用例動態(tài)定義的變量。如果你不知道什么是閉包,我建議你看看 stack overflow 上的這個問題 ,如果這還幫助不了你,你可以發(fā)送電子郵件給我([email protected]),我會嘗試寫一篇關(guān)于閉包的文章。

通過使用閉包,我們可以在測試中動態(tài)地設(shè)置變量,而不需要一次又一次地編寫相同的代碼,F(xiàn)在,如果我們使用go test -v 運(yùn)行我們的測試,我們將得到以下輸出:

=== RUN TestSum

=== RUN TestSum/[1,2,3,4,5]

=== RUN TestSum/[1,2,3,4,-5]

— PASS: TestSum (0.00s)

— PASS: TestSum/[1,2,3,4,5] (0.00s)

— PASS: TestSum/[1,2,3,4,-5] (0.00s)

PASS

ok calhoun.io/testing101 0.005s

這些測試現(xiàn)在用它們的輸入做了標(biāo)記,并且嵌套在 TestSum 測試用例下,使得調(diào)試任何問題都非常容易做到。

譯注:還有一種選擇其實(shí)更常用,那就是表格測試。

示例作為測試

幾乎所有開發(fā)人員的目標(biāo)之一就是編寫易于使用和維護(hù)的代碼。為了實(shí)現(xiàn)這一點(diǎn),包含如何使用代碼的示例通常會有所幫助。Go 的 testing 包提供了幫助定義示例源代碼的功能。作為附加的用途,testing 包還可以測試您的示例,以確保它們在測試過程中輸出您期望的內(nèi)容。

打開 sum_test.go,在文件末尾增加如下代碼:

func ExampleSum() {  
  numbers := []int{5, 5, 5}
  fmt.Println(Sum(numbers))
  // Output:
  // 15
}

然后使用 go test -v 運(yùn)行測試。您現(xiàn)在應(yīng)該在結(jié)果中看到此示例函數(shù),但它是如何被測試的呢?

Go 使用在 ExampleXxx() 函數(shù)底部的 “Output 注釋” 部分來確定預(yù)期的輸出是什么,然后在運(yùn)行測試時,它將實(shí)際輸出與注釋中的預(yù)期輸出進(jìn)行比較,如果不匹配,將觸發(fā)失敗的測試。這樣,我們可以同時編寫測試和示例代碼。

除了創(chuàng)建用于測試的示例外,示例還用于顯示在生成的文檔中。例如,上面的例子可以用來為我們的 testing101 包生成文檔,類似下面的截圖。

更復(fù)雜的例子

在測試足夠的代碼和編寫足夠的示例之后,您最終會發(fā)現(xiàn)某些測試在單個函數(shù)中不容易編寫。發(fā)生這種情況的一個常見原因是,您需要在多次測試之前或之后設(shè)置(setup)或拆卸(teardown)東西。例如,您可能希望從環(huán)境變量獲取數(shù)據(jù)庫 URL,并在運(yùn)行多個測試之前設(shè)置到數(shù)據(jù)庫的連接,而不是單獨(dú)為每個測試重新連接到數(shù)據(jù)庫。

為支持該功能,Go 提供了 TestMain(*testing.M) 的函數(shù),它在需要的時候代替運(yùn)行所有的測試。使用 TestMain() 函數(shù)時,您有機(jī)會在測試運(yùn)行之前或之后插入所需的任何自定義代碼,但唯一需要注意的是必須處理 flag 解析并使用測試結(jié)果調(diào)用 os.Exit()。這聽起來可能很復(fù)雜,但實(shí)際上只有兩行代碼。

flag.Parse()  
os.Exit(m.Run())

讓我們看一個更完整的例子。在我們的 testing101 包中,創(chuàng)建一個名為 db_test.go 的文件,并將下面的代碼添加到其中。

package testing101

import (  
  "flag"
  "fmt"
  "os"
  "testing"
)

var db struct {  
  Url string
}

func TestMain(m *testing.M) {  
  // Pretend to open our DB connection
  db.Url = os.Getenv("DATABASE_URL")
  if db.Url == "" {
    db.Url = "localhost:5432"
  }

  flag.Parse()
  exitCode := m.Run()

  // Pretend to close our DB connection
  db.Url = ""

  // Exit
  os.Exit(exitCode)
}

func TestDatabase(t *testing.T) {  
  // Pretend to use the db
  fmt.Println(db.Url)
}

在這段代碼中,我們首先創(chuàng)建一個名為 db 的全局變量,它是一個包含 Url 的結(jié)構(gòu)體。通常,這將是一個實(shí)際的數(shù)據(jù)庫連接,但對于這個例子,我們只是示例,只設(shè)置了 Url。

接下來在 TestMain() 中,我們假裝通過分析環(huán)境變量 DATABASE_URL 并將其設(shè)置為 db.Url 屬性來打開數(shù)據(jù)庫連接。如果這是一個空字符串,則默認(rèn)為 localhost:5432,Postgres 使用的默認(rèn)端口。

之后我們解析標(biāo)志 (這樣我們的 go test -v 中的 -v 選項可以工作),調(diào)用 m.Run() 并將結(jié)果狀態(tài)碼存儲在 exitCode 中,以便在結(jié)束測試時可以引用它。如果你對退出狀態(tài)代碼不太了解,現(xiàn)在就不重要了。只需記住,我們需要存儲從 m.Run() 返回的狀態(tài)碼,以后再使用它。

在運(yùn)行測試后,我們假裝通過將 db.Url 屬性設(shè)置為空字符串來關(guān)閉數(shù)據(jù)庫連接。

最后,我們使用 os.Exit(exitCode) 退出。這將導(dǎo)致當(dāng)前程序 (我們正在運(yùn)行的測試) 使用我們提供的狀態(tài)代碼退出。通常,除零之外的任何內(nèi)容都將被視為錯誤。

總結(jié)

看完這里的所有內(nèi)容,您可能準(zhǔn)備好了要開始為 Go 編寫的代碼編寫測試。但請記住,這只是表明您可以編寫測試并不意味著您應(yīng)該。過度測試和沒有測試幾乎一樣糟糕,因為它可能導(dǎo)致需要維護(hù)的大量測試代碼。

英文原文: https://www.calhoun.io/how-to-test-with-go/ 或

https://semaphoreci.com/community/tutorials/how-to-test-in-go

 

來自:http://blog.studygolang.com/2017/10/how-to-test-with-go/

 

標(biāo)簽: Google 代碼 電子郵件 數(shù)據(jù)庫

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:關(guān)于云存儲系統(tǒng)的六大技術(shù)分析

下一篇:「Android」Bolts-更簡單的完成線程調(diào)度和任務(wù)管理