Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Who tests the Tests ?
Search
sivchari
February 26, 2026
260
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Who tests the Tests ?
sivchari
February 26, 2026
More Decks by sivchari
See All by sivchari
go test を速くする
sivchari
4
1.5k
govalid ~ Type-safe validation tool ~
sivchari
0
220
Go1.25 リリースパーティ ~ nil pointer bug ~
sivchari
0
220
Google Developer Group - DevFest Tokyo 2025
sivchari
0
220
Go 1.26 リリースパーティ
sivchari
0
300
静的解析 x Kubernetes API Conventions = Kube API Linter ~ ベストプラクティスに準拠したカスタムリソースの作り方と運用 ~
sivchari
0
350
What's GOCACHEPROG ?
sivchari
1
660
gh_extensionsによる快適なOSS生活.pdf
sivchari
0
250
Visualization Go scheduler by gosched-simulator
sivchari
1
750
Featured
See All Featured
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
300
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
2
930
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
270
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
320
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
How Software Deployment tools have changed in the past 20 years
geshan
2
35k
Practical Orchestrator
shlominoach
192
12k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.2k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.2k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.5k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.7k
Mind Mapping
helmedeiros
1
390
Transcript
Who tests the Tests ? ~ Go Conference mini in
Sendai 2026 ~ The Go gopher was designed by Renee French
もくじ • なぜCIは重要であり、ぼくたちはテストを書くのか • 嘘つきなカバレッジ • Mutation Testing • どのように
Mutation Testingを実装するのか • まとめ
自己紹介 渋谷拓真 Go Conference メインオーガナイザー OSS ◦ Kubernetes メンテナー ◦
Argo CD メンテナー Kubernetes 2025 Contributor Award 3Dプリンターにハマってます
なぜCIは重要であり、ぼくたちはテストを書くのか
CIは開発サイクルの最低限の約束
CIは開発サイクルの最低限の約束 CIの成功 = レビュー依頼の準備 ◦ 「CI通ったのでレビューお願いします」 ◦ 「CI失敗しているので直してください」 CIはチーム開発をする上での最低限の約束事として動いている ◦
「新規開発をするから GitHub Actionsから整えるぞ!」
CIは開発サイクルの最低限の約束
CIは開発サイクルの必須の約束 AI Agentが自己回帰する際のチェックポイントになる ◦ 「CIが失敗したので調査をして継続します」 ◦ 「CIが全てPASSしたので終了します」 出力するコードが指数関数的に増える中で自動で品質を担保することはより必 要な世界線で開発をしている
CIホスティングサービスでベースはすぐに整備できる name: Go on: [push] jobs: build: runs-on: ubuntu-latest steps:
- uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version: 1.26 - run: go test ./...
コードの品質を担保するにはどのような手段があるか Linter / Analyzer ◦ go vet ◦ golangci-lint ◦
go fix Test ◦ go test
コードの品質を担保するにはどのような手段があるか Linter / Analyzer ◦ go vet ◦ golangci-lint ◦
go fix Test ◦ go test
なぜテストを書くのか テストはコードに対する実装の意図を明文化するための保証書 ◦ テスト対象が何をやろうとしているか ◦ テスト対象が何をやらないか ◦ ホワイトボックステスト テストはコードに対しての一番近いクライアント ◦
実際のユーザーを擬似的に再現可能 ◦ ブラックボックステスト
嘘つきなカバレッジ
テストによる計測 Goには様々なテスト手法がある ◦ Test ◦ Benchmark ◦ Example ◦ Fuzzing
カバレッジをベースとしコードベース内で検証されている割合を計測する ◦ go test -cover # unit test ◦ go build -cover # e2e test
「ある指標が目標になると、その時点でその指標は “良い指標”ではなくなる」
数値だけを追い求めるケース タイピング速度が速くなると開発生産性が上がる → タイピング速度に固執する PRレビューの粒度は小さいとレビューしやすくていい → PRの数を貢献した指標としてしまうと分割することが目標となる テストのカバレッジを常に 80%維持する →
本質的に必要のないテストケースの増加へつながる
数値だけを追い求めるケース タイピング速度が速くなると開発生産性が上がる → タイピング速度に固執する PRレビューの粒度は小さいとレビューしやすくていい → PRの数を貢献した指標としてしまうと分割することが目標となる テストのカバレッジを常に 80%維持する →
本質的に必要のないテストケースの増加へつながる
100%のカバレッジ!! func Div(a, b int) (int, error) { if b
== 0 { return 0, errors.New(“b must not be zero”) } return a / b, nil } func TestDiv(t *testing.T) { _, _ = Div(1, 1) _, _ = Div(1, 0) }
AI時代だと類似した出来事が起こりうることがある Xのテスト書いて わかりました t.Skipしてる!? ごめんなさい
Mutation Testing
Mutation Testingとは プログラムの一部を 意図的に書き換え 、生成したミュータントに対してテスト を実行し、失敗するかどうかを検証する • 演算子の反転 : *
→ / , - → + , == → != , || → && • 条件の変換 : elseの削除, caseの削除 • ループの変換 : continue → break
Mutation Testingの評価方法 ミューテーションテストの実行後 • テストが失敗 → ミュータントが KILL(成功) • テストが成功
→ ミュータントが SURVIVED(失敗) テストの指標は以下に多くのミュータントを KILLできたかで考える
Mutation Testingの注意事項 実行時間とのトレードオフ • Mutation Testingではソースコードの修正が必須 • 規模が大きいと実行時間も伸びるためどこに対して行うかを考える 100% KILLEDは目指さない
• 1 / 1 → 1 * 1 のように変換しても意味がない場合もある • そのケースが必要かどうかを検討しながら時には Ignoreする 「ある指標が目標になると、その時点でその指標は “良い指標”ではなくなる」
どのように Mutation Testingを実装するのか
Mutation Testingの実装方法 sivchari/gomuの実装方法をベースとします GoのMutation Testingライブラリ overlayを活用しミューテーションテストを並列に実行する インクリメンタル解析を行い差分のみの Mutation Testingを実行可能 PRへのレポート生成と閾値以下のスコアで
FailさせるCIファースト思想
Mutation Testingの挙動 パースして AST生成 ↓ Mutation生成 ↓ Mutationを適用して一時ファイルと overlay.jsonを生成 ↓
go test -overlay=overlay.json ./... を行いKILLED/SURVIVED判定
パースして AST生成 main.go → parser.ParseFile → AST 1 + 1
→
Mutation生成 ast.Inspect(fileAST, func(node ast.Node) bool { for _, mutator :=
range e.mutators { if mutator.CanMutate(node) { mutants = mutator.Mutate(node, fset) } } return true })
Mutation生成 ast.Inspect(fileAST, func(node ast.Node) bool { for _, mutator :=
range e.mutators { if mutator.CanMutate(node) { mutants = mutator.Mutate(node, fset) } } return true })
CanMutate switch node.(type) { case *ast.BinaryExpr: // a + b
case *ast.AssignStmt: // a += b case *ast.IncDecStmt: // a++ return true } return false
Mutation生成 ast.Inspect(fileAST, func(node ast.Node) bool { for _, mutator :=
range e.mutators { if mutator.CanMutate(node) { mutants = mutator.Mutate(node, fset) } } return true })
Mutate switch n := node.(type) { case *ast.BinaryExpr: return m.mutateBinaryExpr(n,
pos) }
Mutate switch n := node.(type) { case *ast.BinaryExpr: return m.mutateBinaryExpr(n,
pos) }
mutateBinaryExpr mutations := m.getArithmeticMutations(expr.Op) mutants := make([]Mutant, 0, len(mutations)) for
_, newOp := range mutations { mutants = append(mutants, Mutant{ Line: pos.Line, Column: pos.Column, Original: expr.Op.String(), // "+" Mutated: newOp.String(), // "-, *, /" }) } return mutants
mutateBinaryExpr mutations := m.getArithmeticMutations(expr.Op) mutants := make([]Mutant, 0, len(mutations)) for
_, newOp := range mutations { mutants = append(mutants, Mutant{ Line: pos.Line, Column: pos.Column, Original: expr.Op.String(), // "+" Mutated: newOp.String(), // "-, *, /" }) } return mutants
getArithmeticMutations switch op { case token.ADD: // + // -,
*, / return []token.Token{token.SUB, token.MUL, token.QUO} ... default: return nil }
Mutation適用とoverlya.jsonの生成 for i, mutant := range mutants { wg.Add(1) go
func(index int, m mutation.Mutant) { defer wg.Done() result := e.runSingleMutation(m, timeout) }(i, mutant) }
Mutation適用とoverlya.jsonの生成 for i, mutant := range mutants { wg.Add(1) go
func(index int, m mutation.Mutant) { defer wg.Done() result := e.runSingleMutation(m, timeout) }(i, mutant) }
mutation listをgoroutineで実行する
Mutation適用とoverlya.jsonの生成 for i, mutant := range mutants { wg.Add(1) go
func(index int, m mutation.Mutant) { defer wg.Done() result := e.runSingleMutation(m, timeout) }(i, mutant) }
runSIngleMutation mutCtx, err := e.overlay.PrepareMutation(mutant) if err := e.checkCompilationWithOverlay(mutCtx); err
!= nil { result.Status = mutation.StatusNotViable result.Error = fmt.Sprintf("Compilation failed: %v", err) return result } return e.runTestWithOverlay(mutCtx, mutant, timeout)
overlayとは?? mutCtx, err := e.overlay.PrepareMutation(mutant) if err := e.checkCompilationWithOverlay(mutCtx); err
!= nil { result.Status = mutation.StatusNotViable result.Error = fmt.Sprintf("Compilation failed: %v", err) return result } return e.runTestWithOverlay(mutCtx, mutant, timeout)
overlayとは?? Go 1.16 で導入された機能で、実際のファイルを変更せずに、仮想的に別のファ イルで置き換える仕組み { "Replace": { "/path/to/main.go": "/tmp/mutant_1/main.go"
} } go test -overlay=overlay.json ./...
なぜgomuでoverlay? 直接ファイルを修正すると途中で失敗すると戻せなくなる 失敗した場合に mutatedした後のソースファイルをデバッグとして残したい 実行時に置き換えるので goroutineで並列or並行に実行しても競合しない
KILLED/SURVIVED判定 mutCtx, err := e.overlay.PrepareMutation(mutant) if err := e.checkCompilationWithOverlay(mutCtx); err
!= nil { result.Status = mutation.StatusNotViable result.Error = fmt.Sprintf("Compilation failed: %v", err) return result } return e.runTestWithOverlay(mutCtx, mutant, timeout)
KILLED/SURVIVED判定 mutCtx, err := e.overlay.PrepareMutation(mutant) if err := e.checkCompilationWithOverlay(mutCtx); err
!= nil { result.Status = mutation.StatusNotViable result.Error = fmt.Sprintf("Compilation failed: %v", err) return result } return e.runTestWithOverlay(mutCtx, mutant, timeout)
runTestWithOverlay cmd := exec.CommandContext( ctx, "go", "test", "-overlay="+mutCtx.OverlayPath, "./..." )
cmd.Dir = testDir if err != nil { result.Status = mutation.StatusKilled } else { result.Status = mutation.StatusSurvived }
まとめ
まとめ • AI時代、加速する環境の中、品質も加速して変わり続ける • テストカバレッジは重要、同様に意味のあるテストかどうかも重要 • Mutation Testingを活用してテストをテストしよう
ありがとうございました!