**本人是第六屆字節(jié)跳動青訓營(后端組)的成員。本文由博主本人整理自該營的日常學習實踐,首發(fā)于稀土掘金:??Go語言工程實踐之測試 | 青訓營
目錄
一、概述
1、回歸測試
2、集成測試
3、單元測試
二、單元測試
1、流程
2、規(guī)則
3、單元測試的例子
4、assert
5、覆蓋率
6、依賴
7、文件處理
三、Mock測試
四、基準測試
一、概述
測試主要包括:回歸測試、集成測試、單元測試。
1、回歸測試
用于驗證已經(jīng)修改或新增功能后,軟件的既有功能是否受到影響。
它主要用于確保軟件在經(jīng)過修改后仍然能正確運行,并且新的更改沒有引入新的錯誤或破壞原有的功能。
比如開發(fā)抖音產(chǎn)品,那么回歸測試就是新增了一部分功能后,由負責質(zhì)量保證的部門手動在終端創(chuàng)造一些特定的場景(比如刷一下抖音,看一下評論等)來檢查功能是否正確。
2、集成測試
用于驗證多個模塊或組件在一起協(xié)同工作時的正確性。
它主要用于檢查不同模塊之間的交互和接口是否正常,以確保整個軟件系統(tǒng)在集成后能夠正常運行。在軟件開發(fā)過程中,通常會將軟件系統(tǒng)劃分為多個模塊或組件,每個模塊負責實現(xiàn)不同的功能。
在集成測試中,這些模塊會被組合在一起,并進行全面的測試,以驗證它們之間的協(xié)作和接口是否正確。
3、單元測試
在開發(fā)階段,開發(fā)者對單獨的函數(shù)模塊進行功能測試。單元測試用于驗證程序中的各個獨立模塊(通常是函數(shù)或方法)是否按照預期進行工作。它是在軟件開發(fā)中最小的測試單位,旨在測試代碼的最小功能單元,以確保每個單元都能正確地完成其預定的功能。
層級從上到下,測試的覆蓋率逐層變大,成本逐層降低。因此可以說,單元測試的覆蓋率一定程度上決定了代碼的質(zhì)量。
- 單元測試的成本較低,覆蓋率較高,可以確保每個單元都能正確工作。
- 集成測試的成本適中,覆蓋率較低,主要用于驗證不同模塊之間的協(xié)作。
- 回歸測試的成本較高,主要用于驗證整個系統(tǒng)的穩(wěn)定性和功能性。
在實際開發(fā)中,這三種測試方法通常會結(jié)合使用,以便在不同的層次和階段上確保軟件的質(zhì)量和穩(wěn)定性。
二、單元測試
1、流程
單元測試主要包括輸入、測試單元、輸出以及校對。

- “單元”的概念比較廣,包括接口、函數(shù)、模塊等等。
- 用最后的校對來保證代碼的功能與我們預期的相符。最后通過輸出和期望值作校對,來驗證代碼的正確性。
- 單元測試一方面可以保證質(zhì)量。每次編寫新代碼并加入了單元測試,在代碼整體覆蓋率足夠的情況下一方面保證了新功能本身的正確性,又未破壞原有的正確性;另一方面可以提升效率,在代碼有bug的情況下通過編寫單測,可以在一個較短的周期內(nèi)定位和修復問題。
2、規(guī)則
(1)所有的測試文件都以 _test.go 結(jié)尾。這樣可以很容易辨別哪些文件是 go 的源代碼,哪些是測試代碼。
(2)單元測試函數(shù)的命名規(guī)范,以Test開頭,且Test后面的第一個字母大寫。如TestDemo。
(3)單元測試提供了一個TestMain函數(shù),TestMain是 Go 語言中測試包(testing)的一個特殊函數(shù),用于在運行測試之前和之后執(zhí)行一些初始化和收尾操作。它不是針對單個測試用例的,而是整個測試包的入口函數(shù)。它可以被用來替代測試函數(shù)的默認入口點 func TestXxx(t *testing.T)
。
TestMain的代碼結(jié)構(gòu)如下,它會在所有測試函數(shù)執(zhí)行之前運行,并且可以在其中進行全局初始化和清理操作。然后,它會調(diào)用 m.Run()
啟動所有測試函數(shù)的執(zhí)行。
因為它是測試包的入口函數(shù),所以只會執(zhí)行一次。
需要注意的是,測試函數(shù)是并行執(zhí)行的,所以不能依賴測試函數(shù)之間的執(zhí)行順序。
3、單元測試的例子
先在demo2.go文件中,創(chuàng)建一個HelloTom函數(shù),預期返回"Tom"。
(這里為了測試,故意寫成返回"Jerry"。)
//demo2.go
package main
func HelloTom() string {
return "Jerry"
}
然后生成測試文件: demo2_test.go。
Tips:如果使用 GoLand 為IDE,快捷鍵 alt+insert ,然后選擇Test file,可以直接生成一個單元測試文件。
在demo2_test.go中編寫以下測試代碼:
package main
import "testing"
func TestHelloTom(t *testing.T) {
output := HelloTom() //運行HelloTom 接收返回值
expectOutput := "Tom" //預期輸出Tom
if output != expectOutput {
t.Errorf("Expected %s do not match actual %s", expectOutput, output)
}
}
通過 if 的方式校對 output 是否與 expected 相等。如果相等,則測試PASS,不相等則FAIL。
除了直接用運算符來判斷,還有更加簡便的方式,即使用 assert。
4、assert
除了使用運算符進行校對,也有很多開源的assert包可以幫助我們實現(xiàn)預期和實際輸出equal或not equal的比較。
導入開源的assert包:
github.com/stretchr/testify/assert
可以在終端使用以下命令獲取依賴(如果沒有下載過,直接引入包會報紅):
?go get github.com/stretchr/testify/assert
調(diào)用 assert.Equal來對預期和實際輸出進行比較。第二個參數(shù)是預期輸出,第三個參數(shù)是實際輸出:
package main
import (
"github.com/stretchr/testify/assert" // 導入開源的assert包
"testing"
)
func TestHelloTom(t *testing.T) {
output := HelloTom() //運行HelloTom 接收返回值
expectOutput := "Tom" //預期輸出Tom
//if output != expectOutput {
// t.Errorf("Expected %s do not match actual %s", expectOutput, output)
//}
assert.Equal(t, expectOutput, output) //直接調(diào)用包中的接口
}
運行結(jié)果:actual 和 expected 不符合,測試FAIL。
若將HelloTom模塊返回的值改為Tom,再次執(zhí)行單元測試:
可見此時單元測試的結(jié)果是PASS。
5、覆蓋率
在我們進行單元測試時需要考慮以下問題:
如何衡量代碼是否經(jīng)過了足夠的測試?如何評價項目的測試水準?如何評估項目是否達到了高水準測試等級?
單元測試的主要評估標準是代碼覆蓋率,覆蓋率越高則證明越多的代碼經(jīng)過了測試。來看下面這個例子:
創(chuàng)建demo3.go,其中編寫了一個判斷分數(shù)是否及格的功能。
package main
func judgePass(score int) bool {
//return score >= 60
if score >= 60 {
return true
}
return false
}
?生成測試文件demo3_test.go,調(diào)用 judgePass 并傳入一個70,將結(jié)果與true校對。
package main
import (
"github.com/stretchr/testify/assert"
"testing"
)
func Test_judgePass(t *testing.T) {
isPass := judgePass(70)
assert.Equal(t, true, isPass)
}
然后運行帶有覆蓋率(coverage)的測試??梢杂妹顔为殰y試某個模塊(如demo3.go):
go test demo3_test.go demo3.go --cover
也可以在GoLand直接操作,點擊右上角的run with coverage進行測試。測試工具會自動運行測試用例,并在執(zhí)行過程中跟蹤被執(zhí)行的代碼行數(shù)。最后,它會計算測試覆蓋率并給出結(jié)果。(注意,這樣執(zhí)行的是該目錄下的所有測試文件,給出的也是整個包的測試覆蓋率結(jié)果。)
最終的代碼覆蓋率為66.7%。這是因為demo3.go中的3條語句在case為70時執(zhí)行2條。
如果在 judgePass 中直接寫成 return score >= 60,那么代碼的覆蓋率就會變成100%。因為這個函數(shù)中所有的語句都被執(zhí)行過了。?
特別注意:直接點擊run with coverage,執(zhí)行的是該目錄下的所有測試文件,給出的也是整個包的測試覆蓋率結(jié)果。如果該目錄下還有其它文件代碼,執(zhí)行測試用例后會發(fā)現(xiàn)覆蓋率降低:
如何提升覆蓋率?
剛才只是測試了70,覆蓋率只有66.7%,還有部分代碼沒有被運行。我們希望提升覆蓋率,因此可以多傳入一些測試用例case。比如這里,再傳入一個50:
coverage達到了 100%。
6、依賴
實際項目中,測試依賴的組件可能會很復雜。比如可能依賴一些數(shù)據(jù)庫,文件,cache等。這些都屬于項目中的強依賴(即是一個模塊對于另一個模塊存在緊密的依賴關(guān)系。當一個組件的實現(xiàn)或功能發(fā)生變化時,會直接影響到依賴它的其他組件的正確性或穩(wěn)定性)。
而單元測試的兩個目標是冪等和穩(wěn)定:
- 冪等:重復運行一個測試,其結(jié)果都是一樣的。
- 穩(wěn)定:單元測試是相互隔離的,在任何時間任何函數(shù)都可運行。
其實,直接寫單元測試可能是不穩(wěn)定的,因為它可能存在一些依賴,如網(wǎng)絡(luò)等。解決這個問題,可以在單元測試時使用mock測試。
7、文件處理
可以用文件依賴來演示一下單元測試中的依賴問題。
首先創(chuàng)建文件 log.txt,其中內(nèi)容如下:
創(chuàng)建demo4.go,編寫函數(shù)用于實現(xiàn)“讀取文件的第一行”這一功能。
package main
import (
"bufio"
"os"
"strings"
)
func ReadFirstLine() string {
// 打開一個文件
open, err := os.Open("log.txt")
// defer關(guān)鍵字,用于延遲函數(shù)的執(zhí)行
// 這里的作用是在函數(shù) ReadFirstLine() 執(zhí)行結(jié)束后即使發(fā)生錯誤或提前返回,
// 也會確保文件資源 open 被及時關(guān)閉,避免資源泄漏。
defer open.Close()
// 判斷是否發(fā)生error
if err != nil {
return ""
}
// 創(chuàng)建文件掃描器scanner
scanner := bufio.NewScanner(open)
for scanner.Scan() {
// 只讀取第一行的內(nèi)容 返回
return scanner.Text() // 用于獲取scanner當前所在位置的文本內(nèi)容
}
return ""
}
func ProcessFirstLine() string {
// 讀取文件中的行
line := ReadFirstLine()
// 把讀到的行進行字符串替換,把11替換成00
destLine := strings.ReplaceAll(line, "11", "00")
return destLine
}
生成單元測試。同時創(chuàng)建log.txt文件進行文件操作。?
package main
import (
"github.com/stretchr/testify/assert"
"testing"
)
func Test(t *testing.T) {
firstLine := ProcessFirstLine()
assert.Equal(t, "line00", firstLine) // 別忘了第二個參數(shù)是expected,第三個是actual
}
運行:
這里對文件 log.txt 的依賴就是強依賴。
一旦文件被別人篡改,那測試文件可能也會受到影響,甚至無法執(zhí)行。這樣就無法達到單元測試冪等、穩(wěn)定的目標。
三、Mock測試
Mock(模擬)是一種測試技術(shù),用于在測試代碼時替代某些依賴項或功能,以便進行可控制和可預測的測試。這種技術(shù)的目的是模擬真實環(huán)境中的特定行為,從而使開發(fā)人員能夠?qū)浖牟煌糠诌M行獨立測試,而不需要依賴其他組件的完整性或穩(wěn)定性。
打樁(Stubbing)是Mock技術(shù)的一種應用。它是在單元測試中使用模擬對象(通常稱為“樁”或“stub”)代替真實的依賴項或功能,以模擬這些依賴項或功能的行為。打樁的目的是在測試代碼的過程中隔離被測試代碼,并使測試更簡單、可控、可重復和高效。
舉個例子,假設(shè)有一個函數(shù)A,它依賴于函數(shù)B的返回結(jié)果。在單元測試函數(shù)A時,我們可以打樁函數(shù)B的行為,使其返回我們預先設(shè)定好的值,而不是實際去調(diào)用函數(shù)B。這樣就能夠獨立地測試函數(shù)A的邏輯,而不必擔心函數(shù)B的實際行為。
演示一下對 demo4.go 中的 ReadFirstLine 進行打樁測試,不再依賴本地文件:
首先引入monkey:bou.ke/monkey
monkey是一個開源的mock測試庫,可以對函數(shù)或方法進行mock。這是monkey中的打樁函數(shù)和卸載打樁函數(shù):
我們就調(diào)用它們來實現(xiàn)mock。在測試文件中編寫以下代碼:
package main
import (
"bou.ke/monkey"
"github.com/stretchr/testify/assert"
"testing"
)
func TestProcessFirstLine(t *testing.T) {
// 調(diào)用打樁函數(shù) 打樁函數(shù)實現(xiàn)了mock的功能
// 對ReadFirstLine進行打樁操作,替換函數(shù)為輸出“l(fā)ine110”
monkey.Patch(ReadFirstLine, func() string {
return "line11"
})
// 使用defer完成打樁函數(shù)的卸載
defer monkey.Unpatch(ReadFirstLine)
// 再次獲取函數(shù)的返回值
firstLine := ProcessFirstLine()
// 通過mock,就避免了對log.txt的強依賴。這個單元測試可以在任何時間任何環(huán)境去執(zhí)行
assert.Equal(t, "line00", firstLine)
}
調(diào)用打樁函數(shù),模擬ReadFirstLine函數(shù)的功能。這樣,在該測試模塊中并沒有實際調(diào)用ReadFirstLine函數(shù),也沒有用到 log.txt ,但是也能完成測試。而且不會因為文件遭到破壞而無法驗證。
四、基準測試
go中也提供了基準測試的框架。在 Go 語言中,基準測試用于衡量代碼的性能(如運行性能和cpu損耗),特別是在處理大量數(shù)據(jù)時的性能表現(xiàn)。在實際中,當遇到代碼性能瓶頸時,為了定位問題經(jīng)常要對代碼做性能分析,這就用到了基準測試?;鶞蕼y試的使用方法類似于單元測試。
創(chuàng)建demo5.go,這是一個模擬隨機選擇服務器的程序:
package main
import "math/rand"
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i + 100
}
}
func Select() int {
// 模擬通過下標來隨機選擇服務器
return ServerIndex[rand.Intn(10)]
}
編寫測試文件,下面寫了兩個基準測試函數(shù):
package main
import (
"testing"
)
func BenchmarkSelect(b *testing.B) {
InitServerIndex()
b.ResetTimer() //定時器重置,因為函數(shù)InitServerIndex不屬于要測試的函數(shù)的損耗,所以計時要把這個時間去掉
for i := 0; i < b.N; i++ {
Select()
}
}
// 基準測試也支持并行
func BenchmarkSelectParallel(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Select()
}
})
}
BenchmarkSelect
:這個基準測試函數(shù)用于測試Select
函數(shù)的性能。在基準測試開始之前,調(diào)用InitServerIndex
函數(shù)進行初始化工作。然后,通過循環(huán)運行Select
函數(shù)b.N
次,b.N
表示測試運行的迭代次數(shù)。
BenchmarkSelectParallel
:這個基準測試函數(shù)也用于測試Select
函數(shù)的性能,不同之處在于它使用了并行測試。通過b.RunParallel
函數(shù),我們可以在多個 goroutine 中并行運行Select
函數(shù)。這樣可以更好地利用多核處理器的性能,加快測試的執(zhí)行速度。
運行結(jié)果:?
基準測試也支持并行執(zhí)行,但是可見并行去做基準測試的情況下,它的性能退化了。原因是Select中用到了rand函數(shù),而rand為了保證全局的隨機性和并發(fā)安全,它持有全局鎖。因此就降低了并發(fā)的性能。
用fastrand可以提升性能。后期如果有隨機場景,推薦使用fastrand。文章來源:http://www.zghlxwxcb.cn/news/detail-617806.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-617806.html
到了這里,關(guān)于【字節(jié)跳動青訓營】后端筆記整理-3 | Go語言工程實踐之測試的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!