241 Views
July 03, 26
スライド概要
Asakusa.go #8のスライドです。
なぜGoのカバレッジはstmtとfnなのか、Goのカバレッジ計測を利用した時に感じた小さな疑念を発端に調査結果をまとめました。Goが選んだ選択と思想、そこから見えてくるカバレッジについて話します。
Asakusa.go #8 なぜGoのカバレッジはstmtとfnなのか Rinrin #asakusago
自己紹介 名前 職種 趣味 歴 Go ひとこと Rinrin フルスタックエンジニア アニメ、ゲーム、キーボード 2年 asakusaは3〜4回遊びに。美味しいもの 多い! 02
目次 導入 2. Goの選択とその理由 3. 仕組み 4. まとめ 1. 03
導入 SECTION 01 04
Goのカバレッジで取れるのはstmtとfnだけ? statement 全体で取れる $ go test -cover coverage: 80.0% of statements function ごとに取れる $ go tool cover -func grade.go:4: Grade 80.0% Vitest は branch も取れる % Stmts | % Branch | % Funcs | % Lines Vitest では branch も取れるのに、なぜGoは取れないのか? 05
Goの選択と その理由 SECTION 02 06
計測にはバイナリ計測とソースコード計測の2系統がある ソースコード計測 バイナリ計測 コンパイル前にソースを 書き換えてカウンタを挿入 ソースコード 実行中のバイナリを外から監視し、 どこが実行されたか記録する コンパイル 実行 Goはどちらを選んだのか。 07
ソースコード計測 方式 ソースコード計測(Go cover) 移植性 高い:AST だけで完結し、環境に依存しない バイナリ計測(gcov, V8) 低い:OS / CPU / debug info に依存し、環境ごとに実装が必要になる "For the new test coverage tool for Go, we took a different approach that avoids dynamic debugging. The idea is simple: Rewrite the package's source code before compilation to add instrumentation..." — The cover story, Rob Pike, The Go Blog (2013) Go標準ライブラリの構文解析・整形パッケージで実現。Pikeが最重視した弱点の移植性を回避できる。 08
計測の単位はブロック ソースコード計測では、AST をどの単位で区切って計測するかを選べる。分岐はソース上に明示的に現れないため、 波括弧で区切られたブロックが自然な単位になる。 単位 ブロック単位(Go cover) 式・条件単位 分かること ブロックが実行されたか branch / condition コスト 軽い 重い 仕組み ブロック先頭にカウンタ1つ && / || を分岐へ展開し、オペランドごとにカウンタ ブロック(basic block)とは、if / for / range / switch / type switch / select、break・continue・goto・ fallthrough、ラベル付き文、ネストした { }、panic() で区切られた区間。 デメリットは、ブロック内部の分岐を細かく計測できないこと。 =Goで branch を計測できず、取れない 09
仕組み SECTION 03 10
計測したいソースコード 例:整数の絶対値を返す Abs 関数。Abs(3)で1回だけテストを実行してみる。 func Abs(n int) int { if n < 0 { return -n } return n } 11
コンパイル前にカウンタ配列を作る 各ブロックの先頭にカウンタを 1 つ挿入。3ブロックなので長さ 3 の配列ができる。 func Abs(n int) int { GoCover.Count[0] = 0 if n < 0 { GoCover.Count[1] = 0 return -n } GoCover.Count[2] = 0 return n } 12
テスト実行時にカウンタ配列を更新 Abs(3) の時、n < 0 は偽なので return -n のブロックは通らない。 func Abs(n int) int { GoCover.Count[0] = 1 if n < 0 { GoCover.Count[1] = 0 return -n } GoCover.Count[2] = 1 return n } 13
集計 Abs(3) を 1 回テストした場合 ブロック ① ② ③ 合計 カウンタ 場所 Count[0] if n < 0 Count[1] return -n Count[2] return n 文 stmt数 1 1 1 3 実行 ✓ ✗ ✓ 2 命令網羅(stmt) = 実行 stmt ÷ 全 stmt = 2 / 3 = 66.7% 実行結果(ツール出力) $ go tool cover -func abs.go:2: Abs 66.7% 14
まとめ SECTION 04 15
Q. なぜGoのカバレッジはstmtとfnなのか? A. ブロック単位のソースコード計装を行っているから 2 つの決定と、その理由 1. 計測方式は ソースコード計測 理由 AST だけで完結し環境に依存しない=移植性が高いから 2. 計測単位は ブロック単位 理由 ソース書き換えでは分岐がソース上に現れず、波括弧で区切られたブロックが自然な単位だから 16
Thank you ご清聴いただき、 ありがとうございました Rinrin / @rin2yh #asakusago 17
参考文献 Rob Pike「The cover story」The Go Blog, 2013 https://go.dev/blog/cover Go cover ツール実装 src/cmd/cover/ https://github.com/golang/go/tree/master/src/cmd/cover 高橋寿一『知識ゼロから学ぶソフトウェアテスト 第3版 ― アジャイル・AI時代の必携教科書』翔泳社, 2024 https://www.shoeisha.co.jp/book/detail/9784798182452 Vitest 公式ドキュメント「Coverage」 https://vitest.dev/guide/coverage.html 18