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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
sivchari
February 26, 2026
240
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
200
Go1.25 リリースパーティ ~ nil pointer bug ~
sivchari
0
190
Google Developer Group - DevFest Tokyo 2025
sivchari
0
200
Go 1.26 リリースパーティ
sivchari
0
280
静的解析 x Kubernetes API Conventions = Kube API Linter ~ ベストプラクティスに準拠したカスタムリソースの作り方と運用 ~
sivchari
0
320
What's GOCACHEPROG ?
sivchari
1
640
gh_extensionsによる快適なOSS生活.pdf
sivchari
0
230
Visualization Go scheduler by gosched-simulator
sivchari
1
730
Featured
See All Featured
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.7k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
700
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
6
1.2k
We Have a Design System, Now What?
morganepeng
55
8.3k
How GitHub (no longer) Works
holman
316
150k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.7k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.8k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Agile that works and the tools we love
rasmusluckow
331
22k
Done Done
chrislema
187
17k
Ten Tips & Tricks for a 🌱 transition
stuffmc
1
240
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を活用してテストをテストしよう
ありがとうございました!