Falešně pozitivní Code coverage v Go

Go, Kvalita kódu

Go nabízí v základní instalaci nástroj, pro získání metriky Code Coverage. Tedy procentuální hodnoty, která říká kolik kódu je pokryto testy. Jenže bez dodatečného parametru, který není příliš znám, může být toto procento mnohem vyšší, než je ve skutečnosti.

Falešně pozitivní Code coverage v Go

Article in English can be found on dev.to/arxeiss/false-positive-go-code-coverage-3k7j

TL;DR: Pro získání metriky code coverage, není potřeba instalovat žádné speciální nástroje. Vše je obsaženo v základní instalaci. Jenže bez přepínače -coverpkg může být výsledné procento pokrytí mnohem vyšší než je ve skutečnosti. Níže je vše ukázáno vše na příkladu.

Jak nainstalovat Go, připravit si prostředí a další je popsáno v  článku Jak začít s jazykem Go.

Showtime!

Za testy v Go se považují soubory, které jsou ve stejném balíčku (složce) jako zdrojový kód balíčku a jméno souboru končí na _test.go. Tyto soubory se ignorují při buildu, ale zahrnují se v testech. Pro názorné ukázky jsem použil Github repozitář arxeiss/go-false-positive-coverage. Ve kterém jsou 2 balíčky, unary a binary a připravené testy.

Krok 1. Falešná naděje, výsledek 100% pokrytí

Po naklonování repozitáře lze spustit příkazy zmíněné níže. Je jasně viditelné, že hlavní balíček main a balíček unary neobsahují žádné testové soubory a výsledné skóre 100 % je spočteno pouze na základě binary balíčku. Pokud tedy balíček testy neobsahuje, vůbec se nezapočítává do skóre. Ostatně stačí si prohlédnout soubor coverage.out a zmínka o souborech mimo binary balíček zde vůbec není. Proto go tool cover zobrazí 100% pokrytí, protože jiné balíčky nezná.

# Spuštění testů pro aktuální a všechny vnořené balíčky a uložení výsledku do coverage.out
go test -covermode=count -coverprofile=coverage.out ./...
# ? github.com/arxeiss/go-false-positive-coverage [no test files] # ok github.com/arxeiss/go-false-positive-coverage/binary 0.002s coverage: 100.0% of statements # ? github.com/arxeiss/go-false-positive-coverage/unary [no test files]

# Spočtení celkového pokrytí testy
go tool cover -func coverage.out
# github.com/arxeiss/go-false-positive-coverage/binary/operations.go:3: Add 100.0% # github.com/arxeiss/go-false-positive-coverage/binary/operations.go:7: Sub 100.0% # github.com/arxeiss/go-false-positive-coverage/binary/operations.go:11: Mul 100.0% # github.com/arxeiss/go-false-positive-coverage/binary/operations.go:15: Div 100.0% # total: (statements) 100.0%

Krok 2. Přidáním testů klesne celkové skóre

V balíčku unary je připraven soubor operations_test.go.bak, stačí odebrat .bak koncovku, spustit opět stejné příkazy jako výše, ale celkové skóre bude nižší! Pouze 71.4 %. Protože byly přidány testy do dalšího balíčku, je do celkového code coverage zahrnut i tento balíček. Jenže v něm nejsou otestovány všechny funkce. Proto celkové pokrytí klesne.

Příklad: Pokud balíček A je plně pokrytý testy a používá funkce z balíčku B, který testy nemá vůbec, bude pokrytí balíčku B 0%! Go totiž nezapočítá do coverage průchod funkcí, pokud je zavolána z jiného balíčku. I to se při použití přepínače -coverpkg změní. Ve výsledku může tedy balíček bez testů být 100% pokrytý díky použití jinými balíčky.

Krok 3. Použití přepínače -coverpkg

Pro získání celkového skóre bez ohledu na přítomnost testů v jednotlivých balíčcích je nutné použít přepínač -coverpkg. Ten říká, které všechny balíčky se mají otestovat. A i přesto, že balíček neobsahuje žádné testy, je započítán a zahrnut do souboru coverage.out.

go test -covermode=count -coverprofile=coverage.out -coverpkg=github.com/arxeiss/go-false-positive-coverage/... ./...
# ?   	github.com/arxeiss/go-false-positive-coverage	[no test files]
# ok  	github.com/arxeiss/go-false-positive-coverage/binary	0.002s	coverage: 44.4% of statements in github.com/arxeiss/go-false-positive-coverage/...
# ok  	github.com/arxeiss/go-false-positive-coverage/unary	0.002s	coverage: 11.1% of statements in github.com/arxeiss/go-false-positive-coverage/...

go tool cover -func coverage.out
# github.com/arxeiss/go-false-positive-coverage/binary/operations.go:3:	Add		100.0%
# github.com/arxeiss/go-false-positive-coverage/binary/operations.go:7:	Sub		100.0%
# github.com/arxeiss/go-false-positive-coverage/binary/operations.go:11:	Mul		100.0%
# github.com/arxeiss/go-false-positive-coverage/binary/operations.go:15:	Div		100.0%
# github.com/arxeiss/go-false-positive-coverage/main.go:10:		main		0.0%
# github.com/arxeiss/go-false-positive-coverage/unary/operations.go:5:	Abs		100.0%
# github.com/arxeiss/go-false-positive-coverage/unary/operations.go:9:	Sin		0.0%
# github.com/arxeiss/go-false-positive-coverage/unary/operations.go:13:	Cos		0.0%
# total:									(statements)	55.6%

Krok 4. Problém s generovaným kódem

Hlavní problém zmíněného přepínače je, že zahrnuje opravdu vše. A pokud je součástí kódu nějaký generovaný kód, třeba Protobuf definice, jsou také zahrnuty. Jenže ty se většinou netestují a berou se tak jak jsou. Toto už nelze vyřešit nějakým přepínačem, a je nutné využít shell skriptů. Jednoduše odstranit veškeré výskyty z coverage.out. Více o problému na StackOverflow.

cat coverage.out | grep -v ".pb.go" > coverage.filtered.out
go tool cover -func coverage.filtered.out

Vlastní zkušenosti s Go, psaním testů v Go nebo měření code coverage sdílejte v komentářích.

K tomuto článku již není možné přidávat další komentáře