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

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

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

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

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

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

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

什么是測(cè)試?

讓我們從最簡(jiǎn)單的問(wèn)題開始 — 什么是測(cè)試?

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

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

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

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

編寫一個(gè)程序測(cè)試

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

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

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

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

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ù)在一個(gè)名為 testing101 的包中。如果您在本地自己編寫代碼,記得導(dǎo)入正確的包。

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

for n := range numbers {

改為

for _, n := range numbers {

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

通過(guò) go test 進(jìn)行測(cè)試

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

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

package testing101

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

接下來(lái)在同一個(gè)包中,創(chuàng)建一個(gè)名為 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)行我們的測(cè)試,所以在終端中切換到 testing101 包所在目錄,并使用下面的命令運(yùn)行測(cè)試。

go test -v

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

=== RUN TestSum

— PASS: TestSum (0.00s)

PASS

ok calhoun.io/testing101 0.005s

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

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

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

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

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

=== 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)該能夠開始對(duì)所有代碼進(jìn)行一些基本測(cè)試,但如果需要為同一函數(shù)添加更多測(cè)試用例,或者需要構(gòu)造自己的類型來(lái)測(cè)試代碼,會(huì)發(fā)生什么情況?

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

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

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

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

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))
    }
  }
}

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

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

=== 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

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

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

示例作為測(cè)試

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

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

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

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

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

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

更復(fù)雜的例子

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

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

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

讓我們看一個(gè)更完整的例子。在我們的 testing101 包中,創(chuàng)建一個(gè)名為 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)建一個(gè)名為 db 的全局變量,它是一個(gè)包含 Url 的結(jié)構(gòu)體。通常,這將是一個(gè)實(shí)際的數(shù)據(jù)庫(kù)連接,但對(duì)于這個(gè)例子,我們只是示例,只設(shè)置了 Url。

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

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

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

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

總結(jié)

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

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

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

 

來(lái)自:http://blog.studygolang.com/2017/10/how-to-test-with-go/

 

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

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

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

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