/note/tech

GoにおけるMutation Testingの実践チャレンジ

要約:

■ 1. 発表概要

  • 発表者: 伊藤瑛(DeNA エンジニアリング室 開発デザイングループマネージャー)
  • イベント: Go Junction #1(2026年3月23日)
  • テーマ: Goにおけるテスト品質測定手法としてのMutation Testingと、独自ツール「rmut」の設計・実装

■ 2. Mutation Testingの概念

  • 定義: 意図的な不具合(ミュータント)をコードに埋め込み、テストの失敗可否でテスト品質を測定する手法
  • 用語:
    • survived: ミュータント埋め込み後もテストが通過した状態
    • killed: ミュータント埋め込みによりテストが失敗した状態
  • カバレッジとの違い: カバレッジはテストの通過確認のみを行うが、Mutation Testingは不具合検出能力を測定する

■ 3. ユースケース

  • 年齢判定の例:
    • >>= に変更しても既存テストが通過する問題を検出
    • テストの不十分さを機械的に発見できる
  • Query Builderの例:
    • 複雑なSQL条件テストにおけるテストデータの網羅性を確認できる
  • 検出可能な問題:
    • テストデータ・フィクスチャの網羅性不足
    • Assertionの正確性欠如
    • ライブラリ動作の誤解(Equalsの振る舞いなど)

■ 4. rmutの設計と実装

  • 高速化アプローチ:
    • 1回のAST書き換えで全ミューテーションを埋め込む
    • TestMainでテストをループさせ、ループごとに異なるmutatorを起動させる
    • 通常の「mutant1つごとにTest Suite全体を実行」する構造を改善
  • 実装上の課題:
    • 型情報の確認: 書き換え対象式の型をpackages.Loadで取得する必要がある
    • Untyped型への対応: リテラル値だけからは型判定ができない場合がある
    • Compile Error回避: 親ノードのコンテキスト分析が必須
  • 無限ループ対策:
    • LoopCounter: for文検出時にループ回数制限を埋め込む
    • Timeout: Go標準の機構を活用
    • 再帰やgotoによる無限ループには未対応
  • 運用上の工夫:
    • sync.*系ミューテーションを除外(デッドロック回避)
    • gitのdiffとcallgraph解析で適用範囲を限定
    • カスタムmutator用プラグイン機構(squirrel等の非標準ライブラリ対応)

■ 5. 残存する課題

  • Compile Error: 予期しない構文で発生する
  • 実行時間の不安定性: ミューテーション数に応じて増加する
  • 無限ループの遭遇: パニック回避の限界がある
  • Survivedミューテーションの意味: ロギングや例外処理では無意味化する場合がある
  • プラグイン配布の困難: go/pluginの制限、WASI対応の困難がある

■ 6. 結論

  • テスト品質の評価を「Testの品質を測定しよう」という観点から行うべき
  • AI時代においてテスト生成の「正しさ」を計測する手法として重要性が増している
  • 参考資源: Stryker Mutator、pitest(Java向けツール)、Google Research「State of Mutation Testing at Google」

MEMO: