Mục lục:
Tổng quan
Build một binary để làm coverage profiling
Chạy một binary đã được instrument coverage
Làm việc với các tệp dữ liệu coverage
Câu hỏi thường gặp
Tài nguyên
Bảng thuật ngữ
Bắt đầu từ Go 1.20, Go hỗ trợ thu thập coverage profile từ các ứng dụng và từ integration test — những bài test lớn và phức tạp hơn dành cho chương trình Go.
Tổng quan
Go cung cấp hỗ trợ dễ dùng để thu thập coverage profile ở mức unit test của package thông qua lệnh “go test -coverprofile=... <pkg_target>”. Bắt đầu từ Go 1.20, bạn nay có thể thu thập coverage profile cho các integration test lớn hơn: những bài test nặng và phức tạp hơn, thực hiện nhiều lần chạy của một binary ứng dụng cho trước.
Với unit test, việc thu thập một coverage profile và tạo báo cáo cần hai bước: chạy go test -coverprofile=..., rồi gọi go tool cover {-func,-html} để tạo báo cáo.
Với integration test, cần ba bước: bước build, bước chạy (có thể gồm nhiều lần gọi binary từ bước build), và cuối cùng là bước báo cáo, như mô tả dưới đây.
Build một binary để làm coverage profiling
Để build một ứng dụng phục vụ việc thu thập coverage profile, hãy truyền cờ -cover khi gọi go build trên target binary của ứng dụng. Xem phần bên dưới để có ví dụ về một lệnh go build -cover. Binary tạo ra sau đó có thể chạy kèm một cấu hình biến môi trường để thu lại coverage profile (xem phần tiếp theo về chạy).
Cách các package được chọn để instrument
Trong một lần gọi “go build -cover”, lệnh Go sẽ chọn các package trong module chính để làm coverage profiling; các package khác tham gia vào quá trình build (các phụ thuộc liệt kê trong go.mod, hoặc các package thuộc thư viện chuẩn của Go) sẽ không được đưa vào theo mặc định.
Ví dụ, đây là một chương trình đồ chơi gồm một package main, một package greetings cục bộ thuộc module chính, và một tập các package import từ bên ngoài module, trong đó có (cùng nhiều package khác) rsc.io/quote và fmt (link tới chương trình đầy đủ).
$ cat go.mod
module mydomain.com
go 1.20
require rsc.io/quote v1.5.2
require (
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
rsc.io/sampler v1.3.0 // indirect
)
$ cat myprogram.go
package main
import (
"fmt"
"mydomain.com/greetings"
"rsc.io/quote"
)
func main() {
fmt.Printf("I say %q and %q\n", quote.Hello(), greetings.Goodbye())
}
$ cat greetings/greetings.go
package greetings
func Goodbye() string {
return "see ya"
}
$ go build -cover -o myprogram.exe .
$
Nếu bạn build chương trình này với cờ dòng lệnh “-cover” rồi chạy nó, đúng hai package sẽ được đưa vào profile: main và mydomain.com/greetings; các package phụ thuộc còn lại sẽ bị loại trừ.
Nếu bạn muốn kiểm soát nhiều hơn xem package nào được đưa vào coverage, hãy build với cờ “-coverpkg”. Ví dụ:
$ go build -cover -o myprogramMorePkgs.exe -coverpkg=io,mydomain.com,rsc.io/quote .
$
Trong lệnh build trên, package main từ mydomain.com cùng với các package rsc.io/quote và io được chọn để profiling; vì mydomain.com/greetings không được liệt kê cụ thể nên nó sẽ bị loại khỏi profile, dù nó nằm trong module chính.
Chạy một binary đã được instrument coverage
Các binary được build với “-cover” sẽ ghi ra các tệp dữ liệu profile khi kết thúc thực thi, vào một thư mục được chỉ định qua biến môi trường GOCOVERDIR. Ví dụ:
$ go build -cover -o myprogram.exe myprogram.go
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
I say "Hello, world." and "see ya"
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$
Hãy chú ý hai tệp đã được ghi vào thư mục somedata: các tệp (nhị phân) này chứa kết quả coverage. Xem phần báo cáo sau đây để biết thêm cách tạo ra kết quả đọc được bởi con người từ các tệp dữ liệu này.
Nếu biến môi trường GOCOVERDIR không được đặt, một binary đã được instrument coverage vẫn thực thi đúng, nhưng sẽ phát ra cảnh báo. Ví dụ:
$ ./myprogram.exe
warning: GOCOVERDIR not set, no coverage data emitted
I say "Hello, world." and "see ya"
$
Các bài test có nhiều lần chạy
Trong nhiều trường hợp, integration test có thể gồm nhiều lần chạy chương trình; khi chương trình được build với “-cover”, mỗi lần chạy sẽ tạo ra một tệp dữ liệu mới. Ví dụ
$ mkdir somedata2
$ GOCOVERDIR=somedata2 ./myprogram.exe // first run
I say "Hello, world." and "see ya"
$ GOCOVERDIR=somedata2 ./myprogram.exe -flag // second run
I say "Hello, world." and "see ya"
$ ls somedata2
covcounters.890814fca98ac3a4d41b9bd2a7ec9f7f.2456041.1670259309405583534
covcounters.890814fca98ac3a4d41b9bd2a7ec9f7f.2456047.1670259309410891043
covmeta.890814fca98ac3a4d41b9bd2a7ec9f7f
$
Các tệp dữ liệu coverage xuất ra có hai loại: tệp meta-data (chứa những thứ không đổi giữa các lần chạy, như tên tệp mã nguồn và tên hàm), và tệp dữ liệu counter (ghi lại những phần của chương trình đã được thực thi).
Trong ví dụ trên, lần chạy đầu tạo ra hai tệp (counter và meta), còn lần chạy thứ hai chỉ tạo ra một tệp dữ liệu counter: vì meta-data không thay đổi giữa các lần chạy nên nó chỉ cần được ghi một lần.
Làm việc với các tệp dữ liệu coverage
Go 1.20 giới thiệu một công cụ mới, ‘covdata’, dùng để đọc và thao tác với các tệp dữ liệu coverage từ một thư mục GOCOVERDIR.
Công cụ covdata của Go chạy ở nhiều chế độ khác nhau. Dạng tổng quát của một lệnh gọi công cụ covdata như sau
$ go tool covdata <mode> -i=<dir1,dir2,...> ...flags...
trong đó cờ “-i” cung cấp danh sách các thư mục để đọc, mỗi thư mục sinh ra từ một lần thực thi của một binary đã được instrument coverage (thông qua GOCOVERDIR).
Tạo báo cáo coverage profile
Phần này bàn về cách dùng “go tool covdata” để tạo ra các báo cáo đọc được bởi con người từ các tệp dữ liệu coverage.
Báo cáo phần trăm câu lệnh được phủ
Để báo cáo chỉ số “phần trăm câu lệnh được phủ” cho từng package đã được instrument, hãy dùng lệnh “go tool covdata percent -i=<directory>”. Dùng lại ví dụ ở phần chạy bên trên:
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata percent -i=somedata
main coverage: 100.0% of statements
mydomain.com/greetings coverage: 100.0% of statements
$
Các phần trăm “câu lệnh được phủ” ở đây tương ứng trực tiếp với những gì go test -cover báo cáo.
Chuyển sang định dạng văn bản cũ
Bạn có thể chuyển các tệp dữ liệu coverage nhị phân sang định dạng văn bản cũ do “go test -coverprofile=<outfile>” tạo ra, bằng cách dùng bộ chọn textfmt của covdata. Tệp văn bản thu được sau đó có thể dùng với “go tool cover -func” hoặc “go tool cover -html” để tạo thêm báo cáo. Ví dụ:
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata textfmt -i=somedata -o profile.txt
$ cat profile.txt
mode: set
mydomain.com/myprogram.go:10.13,12.2 1 1
mydomain.com/greetings/greetings.go:3.23,5.2 1 1
$ go tool cover -func=profile.txt
mydomain.com/greetings/greetings.go:3: Goodbye 100.0%
mydomain.com/myprogram.go:10: main 100.0%
total: (statements) 100.0%
$
Gộp dữ liệu
Lệnh con merge của “go tool covdata” có thể dùng để gộp các profile từ nhiều thư mục dữ liệu lại với nhau.
Ví dụ, hãy xét một chương trình chạy trên cả macOS lẫn Windows. Tác giả chương trình này có thể muốn kết hợp các coverage profile từ những lần chạy riêng biệt trên mỗi hệ điều hành thành một tập profile duy nhất, nhằm tạo ra một bản tóm tắt coverage đa nền tảng. Ví dụ:
$ ls windows_datadir
covcounters.f3833f80c91d8229544b25a855285890.1025623.1667481441036838252
covcounters.f3833f80c91d8229544b25a855285890.1025628.1667481441042785007
covmeta.f3833f80c91d8229544b25a855285890
$ ls macos_datadir
covcounters.b245ad845b5068d116a4e25033b429fb.1025358.1667481440551734165
covcounters.b245ad845b5068d116a4e25033b429fb.1025364.1667481440557770197
covmeta.b245ad845b5068d116a4e25033b429fb
$ ls macos_datadir
$ mkdir merged
$ go tool covdata merge -i=windows_datadir,macos_datadir -o merged
$
Thao tác merge ở trên sẽ kết hợp dữ liệu từ các thư mục đầu vào được chỉ định và ghi một tập tệp dữ liệu đã gộp mới vào thư mục “merged”.
Chọn package
Hầu hết các lệnh “go tool covdata” đều hỗ trợ cờ “-pkg” để chọn package như một phần của thao tác; đối số của “-pkg” có cùng dạng với đối số của cờ “-coverpkg” của lệnh Go. Ví dụ:
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata percent -i=somedata -pkg=mydomain.com/greetings
mydomain.com/greetings coverage: 100.0% of statements
$ go tool covdata percent -i=somedata -pkg=nonexistentpackage
$
Cờ “-pkg” có thể dùng để chọn đúng tập con các package mà bạn quan tâm cho một báo cáo cụ thể.
Câu hỏi thường gặp
- Làm sao để yêu cầu instrument coverage cho mọi package được import có nhắc đến trong tệp
go.modcủa tôi - Tôi có thể dùng
go build -coverở chế độ GOPATH/GO111MODULE=off không? - Nếu chương trình của tôi panic, dữ liệu coverage có được ghi không?
-coverpkg=maincó chọn package main của tôi để profiling không?
Làm sao để yêu cầu instrument coverage cho mọi package được import có nhắc đến trong tệp go.mod của tôi
Theo mặc định, go build -cover sẽ instrument mọi package của module chính để lấy coverage, nhưng sẽ không instrument các import nằm ngoài module chính (ví dụ các package thư viện chuẩn hoặc các import liệt kê trong go.mod). Một cách để yêu cầu instrument mọi phụ thuộc không thuộc thư viện chuẩn là đưa đầu ra của go list vào -coverpkg. Dưới đây là một ví dụ, cũng dùng lại chương trình ví dụ đã nêu ở trên:
$ go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' -deps . | paste -sd "," > pkgs.txt
$ go build -o myprogram.exe -coverpkg=`cat pkgs.txt` .
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
$ go tool covdata percent -i=somedata
golang.org/x/text/internal/tag coverage: 78.4% of statements
golang.org/x/text/language coverage: 35.5% of statements
mydomain.com coverage: 100.0% of statements
mydomain.com/greetings coverage: 100.0% of statements
rsc.io/quote coverage: 25.0% of statements
rsc.io/sampler coverage: 86.7% of statements
$
Tôi có thể dùng go build -cover ở chế độ GO111MODULE=off không?
Có, go build -cover hoạt động được với GO111MODULE=off. Khi build một chương trình ở chế độ GO111MODULE=off, chỉ package được nêu tên cụ thể làm target trên dòng lệnh mới được instrument để profiling. Dùng cờ -coverpkg để đưa thêm package vào profile.
Nếu chương trình của tôi panic, dữ liệu coverage có được ghi không?
Các chương trình build bằng go build -cover chỉ ghi ra dữ liệu profile đầy đủ ở cuối quá trình thực thi nếu chương trình gọi os.Exit() hoặc trở về bình thường từ main.main. Nếu một chương trình kết thúc bằng một panic không được recover, hoặc nếu chương trình gặp một ngoại lệ chí mạng (như segmentation violation, chia cho không, v.v.), dữ liệu profile từ các câu lệnh đã thực thi trong lần chạy đó sẽ bị mất.
-coverpkg=main có chọn package main của tôi để profiling không?
Cờ -coverpkg nhận một danh sách import path, không phải danh sách tên package. Nếu bạn muốn chọn package main của mình để instrument coverage, hãy xác định nó bằng import path, không phải bằng tên. Ví dụ (dùng chương trình ví dụ này):
$ go list -m
mydomain.com
$ go build -coverpkg=main -o oops.exe .
warning: no packages being built depend on matches for pattern main
$ go build -coverpkg=mydomain.com -o myprogram.exe .
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
I say "Hello, world." and "see ya"
$ go tool covdata percent -i=somedata
mydomain.com coverage: 100.0% of statements
$
Tài nguyên
- Bài blog giới thiệu coverage cho unit test trong Go 1.2:
- Coverage profiling cho unit test được giới thiệu như một phần của bản phát hành Go 1.2; xem bài blog này để biết chi tiết.
- Tài liệu:
- Tài liệu package
cmd/gomô tả các cờ build và test liên quan đến coverage.
- Tài liệu package
- Chi tiết kỹ thuật:
Bảng thuật ngữ
unit test: Các bài test nằm trong tệp *_test.go gắn với một package Go cụ thể, sử dụng package testing của Go.
integration test: Một bài test toàn diện hơn, nặng hơn cho một ứng dụng hoặc binary cho trước. Integration test thường gồm việc build một chương trình hoặc một tập chương trình, rồi thực hiện một loạt lần chạy các chương trình đó với nhiều đầu vào và kịch bản khác nhau, dưới sự điều khiển của một bộ khung kiểm thử vốn có thể dựa hoặc không dựa trên package testing của Go.