☰ Mục lục series
Tài liệu Go tiếng Việt
  1. Bắt đầu (Getting started)
    1. Tổng quan
    2. Tải về và cài đặt
    3. Bắt đầu với Go
    4. Quản lý các bản cài đặt Go
    5. Plugin trình soạn thảo và IDE
  2. Dạo quanh Go (A Tour of Go)
    1. Chào mừng
    2. Package, biến và hàm
    3. Điều khiển luồng
    4. Struct, slice và map
    5. Method và interface
    6. Generics
    7. Xử lý đồng thời
  3. Hướng dẫn (Tutorials)
    1. Danh sách hướng dẫn
    2. Tạo một Go module
    3. Gọi mã của bạn từ một module khác
    4. Trả về và xử lý lỗi
    5. Trả về lời chào ngẫu nhiên
    6. Trả về lời chào cho nhiều người
    7. Thêm một test
    8. Biên dịch và cài đặt ứng dụng
    9. Kết luận
    10. Bắt đầu với workspace nhiều module
    11. Phát triển RESTful API với Go và Gin
    12. Truy cập cơ sở dữ liệu quan hệ
    13. Bắt đầu với generics
    14. Bắt đầu với fuzzing
    15. Làm việc với JSON
    16. Tìm và sửa các dependency có lỗ hổng với govulncheck
    17. Tìm và khắc phục phụ thuộc có lỗ hổng với VS Code Go
    18. Viết ứng dụng web
  4. Viết code Go
    1. Cách viết mã Go
    2. Effective Go
    3. Doc Comment trong Go
  5. Module
    1. Phát triển và publish module
    2. Quản lý phụ thuộc
    3. Tổ chức một module Go
    4. Quản lý mã nguồn của module
    5. Cách đánh số phiên bản module
    6. Quy trình phát hành và đánh phiên bản module
    7. Publish một module
    8. Phát triển một bản cập nhật major version
    9. Tham chiếu tệp go.mod
    10. Ngừng hỗ trợ 'go get' cho việc cài đặt tệp thực thi
  6. Cơ sở dữ liệu (Database)
    1. Truy cập cơ sở dữ liệu quan hệ
    2. Mở một handle cơ sở dữ liệu
    3. Thực thi câu lệnh SQL không trả về dữ liệu
    4. Truy vấn dữ liệu
    5. Dùng prepared statement
    6. Thực thi transaction
    7. Huỷ các thao tác đang chạy
    8. Quản lý kết nối
    9. Tránh rủi ro SQL injection
  7. Chẩn đoán và hiệu năng
    1. Chẩn đoán
    2. Hướng dẫn về Go Garbage Collector
    3. Tối ưu hoá dựa trên profile
    4. Data Race Detector
    5. Gỡ lỗi mã Go bằng GDB
    6. Hỗ trợ coverage profiling cho integration test
  8. Bảo mật (Security)
    1. Tổng quan bảo mật
    2. Thực hành tốt về bảo mật cho lập trình viên Go
    3. Quản lý lỗ hổng trong Go
    4. Cơ sở dữ liệu lỗ hổng của Go
    5. Quét lỗ hổng trong IDE
    6. Chính sách CNA của Go
    7. Go Fuzzing
    8. Chi tiết kỹ thuật của Go Fuzzing
    9. Chính sách bảo mật của Go
    10. Threat Model của Go
    11. Các quyết định về bảo mật của Go
    12. Tuân thủ FIPS 140-3
  9. Công cụ và toolchain
    1. Tài liệu về các lệnh
    2. Về lệnh go
    3. Go Toolchains
    4. Telemetry của Go
    5. Cài đặt Go từ mã nguồn
    6. Cài đặt và sử dụng gccgo
  10. Tham khảo
    1. Câu hỏi thường gặp (FAQ)
    2. Go 1 và tương lai của các chương trình Go
    3. Hướng dẫn chuyển đổi sang encoding/json/v2
    4. Hướng dẫn đóng góp
    5. Đóng góp cho frontend gccgo
  11. Phụ lục
    1. Bảng thuật ngữ
Chẩn đoán và hiệu năng

Hỗ trợ coverage profiling cho integration test

Bản dịch từ go.dev/doc/build-cover

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

  1. 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
  2. Tôi có thể dùng go build -cover ở chế độ GOPATH/GO111MODULE=off không?
  3. Nếu chương trình của tôi panic, dữ liệu coverage có được ghi không?
  4. -coverpkg=main có 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/go mô tả các cờ build và test liên quan đến coverage.
  • 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.

Nội dung gốc: Go Documentation © The Go Authors, giấy phép CC BY 3.0. Bản dịch và ghi chú: Lê Văn Dũng.