Go 語言 測試覆蓋率

2023-03-14 16:59 更新

原文鏈接:https://gopl-zh.github.io/ch11/ch11-03.html


11.3. 測試覆蓋率

就其性質(zhì)而言,測試不可能是完整的。計(jì)算機(jī)科學(xué)家Edsger Dijkstra曾說過:“測試能證明缺陷存在,而無法證明沒有缺陷。”再多的測試也不能證明一個(gè)程序沒有BUG。在最好的情況下,測試可以增強(qiáng)我們的信心:代碼在很多重要場景下是可以正常工作的。

對待測程序執(zhí)行的測試的程度稱為測試的覆蓋率。測試覆蓋率并不能量化——即使最簡單的程序的動(dòng)態(tài)也是難以精確測量的——但是有啟發(fā)式方法來幫助我們編寫有效的測試代碼。

這些啟發(fā)式方法中,語句的覆蓋率是最簡單和最廣泛使用的。語句的覆蓋率是指在測試中至少被運(yùn)行一次的代碼占總代碼數(shù)的比例。在本節(jié)中,我們使用go test命令中集成的測試覆蓋率工具,來度量下面代碼的測試覆蓋率,幫助我們識別測試和我們期望間的差距。

下面的代碼是一個(gè)表格驅(qū)動(dòng)的測試,用于測試第七章的表達(dá)式求值程序:

gopl.io/ch7/eval

func TestCoverage(t *testing.T) {
    var tests = []struct {
        input string
        env   Env
        want  string // expected error from Parse/Check or result from Eval
    }{
        {"x % 2", nil, "unexpected '%'"},
        {"!true", nil, "unexpected '!'"},
        {"log(10)", nil, `unknown function "log"`},
        {"sqrt(1, 2)", nil, "call to sqrt has 2 args, want 1"},
        {"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
        {"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"},
        {"5 / 9 * (F - 32)", Env{"F": -40}, "-40"},
    }

    for _, test := range tests {
        expr, err := Parse(test.input)
        if err == nil {
            err = expr.Check(map[Var]bool{})
        }
        if err != nil {
            if err.Error() != test.want {
                t.Errorf("%s: got %q, want %q", test.input, err, test.want)
            }
            continue
        }
        got := fmt.Sprintf("%.6g", expr.Eval(test.env))
        if got != test.want {
            t.Errorf("%s: %v => %s, want %s",
                test.input, test.env, got, test.want)
        }
    }
}

首先,我們要確保所有的測試都正常通過:

$ go test -v -run=Coverage gopl.io/ch7/eval
=== RUN TestCoverage
--- PASS: TestCoverage (0.00s)
PASS
ok      gopl.io/ch7/eval         0.011s

下面這個(gè)命令可以顯示測試覆蓋率工具的使用用法:

$ go tool cover
Usage of 'go tool cover':
Given a coverage profile produced by 'go test':
    go test -coverprofile=c.out

Open a web browser displaying annotated source code:
    go tool cover -html=c.out
...

go tool命令運(yùn)行Go工具鏈的底層可執(zhí)行程序。這些底層可執(zhí)行程序放在$GOROOT/pkg/tool/${GOOS}_${GOARCH}目錄。因?yàn)橛?code>go build命令的原因,我們很少直接調(diào)用這些底層工具。

現(xiàn)在我們可以用-coverprofile標(biāo)志參數(shù)重新運(yùn)行測試:

$ go test -run=Coverage -coverprofile=c.out gopl.io/ch7/eval
ok      gopl.io/ch7/eval         0.032s      coverage: 68.5% of statements

這個(gè)標(biāo)志參數(shù)通過在測試代碼中插入生成鉤子來統(tǒng)計(jì)覆蓋率數(shù)據(jù)。也就是說,在運(yùn)行每個(gè)測試前,它將待測代碼拷貝一份并做修改,在每個(gè)詞法塊都會(huì)設(shè)置一個(gè)布爾標(biāo)志變量。當(dāng)被修改后的被測試代碼運(yùn)行退出時(shí),將統(tǒng)計(jì)日志數(shù)據(jù)寫入c.out文件,并打印一部分執(zhí)行的語句的一個(gè)總結(jié)。(如果你需要的是摘要,使用go test -cover。)

如果使用了-covermode=count標(biāo)志參數(shù),那么將在每個(gè)代碼塊插入一個(gè)計(jì)數(shù)器而不是布爾標(biāo)志量。在統(tǒng)計(jì)結(jié)果中記錄了每個(gè)塊的執(zhí)行次數(shù),這可以用于衡量哪些是被頻繁執(zhí)行的熱點(diǎn)代碼。

為了收集數(shù)據(jù),我們運(yùn)行了測試覆蓋率工具,打印了測試日志,生成一個(gè)HTML報(bào)告,然后在瀏覽器中打開(圖11.3)。

$ go tool cover -html=c.out


綠色的代碼塊被測試覆蓋到了,紅色的則表示沒有被覆蓋到。為了清晰起見,我們將背景紅色文本的背景設(shè)置成了陰影效果。我們可以馬上發(fā)現(xiàn)unary操作的Eval方法并沒有被執(zhí)行到。如果我們針對這部分未被覆蓋的代碼添加下面的測試用例,然后重新運(yùn)行上面的命令,那么我們將會(huì)看到那個(gè)紅色部分的代碼也變成綠色了:

{"-x * -x", eval.Env{"x": 2}, "4"}

不過兩個(gè)panic語句依然是紅色的。這是沒有問題的,因?yàn)檫@兩個(gè)語句并不會(huì)被執(zhí)行到。

實(shí)現(xiàn)100%的測試覆蓋率聽起來很美,但是在具體實(shí)踐中通常是不可行的,也不是值得推薦的做法。因?yàn)槟侵荒苷f明代碼被執(zhí)行過而已,并不意味著代碼就是沒有BUG的;因?yàn)閷τ谶壿嫃?fù)雜的語句需要針對不同的輸入執(zhí)行多次。有一些語句,例如上面的panic語句則永遠(yuǎn)都不會(huì)被執(zhí)行到。另外,還有一些隱晦的錯(cuò)誤在現(xiàn)實(shí)中很少遇到也很難編寫對應(yīng)的測試代碼。測試從本質(zhì)上來說是一個(gè)比較務(wù)實(shí)的工作,編寫測試代碼和編寫應(yīng)用代碼的成本對比是需要考慮的。測試覆蓋率工具可以幫助我們快速識別測試薄弱的地方,但是設(shè)計(jì)好的測試用例和編寫應(yīng)用代碼一樣需要嚴(yán)密的思考。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號