Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Who tests the Tests ?

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for sivchari sivchari
February 26, 2026
46

Who tests the Tests ?

Avatar for sivchari

sivchari

February 26, 2026
Tweet

More Decks by sivchari

Transcript

  1. Who tests the Tests ? ~ Go Conference mini in

    Sendai 2026 ~ The Go gopher was designed by Renee French
  2. 自己紹介 渋谷拓真 Go Conference メインオーガナイザー OSS ◦ Kubernetes メンテナー ◦

    Argo CD メンテナー Kubernetes 2025 Contributor Award 3Dプリンターにハマってます
  3. 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 ./...
  4. テストによる計測 Goには様々なテスト手法がある ◦ Test ◦ Benchmark ◦ Example ◦ Fuzzing

    カバレッジをベースとしコードベース内で検証されている割合を計測する ◦ go test -cover # unit test ◦ go build -cover # e2e test
  5. 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) }
  6. Mutation Testingの評価方法 ミューテーションテストの実行後 • テストが失敗 → ミュータントが KILL(成功) • テストが成功

    → ミュータントが SURVIVED(失敗) テストの指標は以下に多くのミュータントを KILLできたかで考える
  7. Mutation Testingの注意事項 実行時間とのトレードオフ • Mutation Testingではソースコードの修正が必須 • 規模が大きいと実行時間も伸びるためどこに対して行うかを考える 100% KILLEDは目指さない

    • 1 / 1 → 1 * 1 のように変換しても意味がない場合もある • そのケースが必要かどうかを検討しながら時には Ignoreする 「ある指標が目標になると、その時点でその指標は “良い指標”ではなくなる」
  8. 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 })
  9. 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 })
  10. CanMutate switch node.(type) { case *ast.BinaryExpr: // a + b

    case *ast.AssignStmt: // a += b case *ast.IncDecStmt: // a++ return true } return false
  11. 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 })
  12. 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
  13. 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
  14. getArithmeticMutations switch op { case token.ADD: // + // -,

    *, / return []token.Token{token.SUB, token.MUL, token.QUO} ... default: return nil }
  15. 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) }
  16. 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) }
  17. 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) }
  18. 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)
  19. 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)
  20. 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)
  21. 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)
  22. 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 }