176 Views
March 23, 26
スライド概要
Go Junction #1 で発表した資料となります。
DeNA が社会の技術向上に貢献するため、業務で得た知見を積極的に外部に発信する、DeNA 公式のアカウントです。DeNA エンジニアの登壇資料をお届けします。
GoにおけるMutation Testingの 実践チャレンジ DeNA エンジニアリング室開発デザイングループ(SWET) 伊藤 瑛 © DeNA Co., Ltd. 1
自己紹介 ● 伊藤 瑛 ○ ● https://github.com/akito0107 DeNA エンジニアリング室開発デザイングループマネージャー SWETと呼ばれていたところ ● ● © DeNA Co., Ltd. 好きな言語など ○ Go, TypeScript ○ LEAN4 Web系全般の開発をやっています(それとGL) 2
本日のアジェンダ ● Mutation Testingとは何か ● Mutation Testingのユースケース ● rmut (Mutation Testing Tool) の設計と実装 ● rmutの課題 © DeNA Co., Ltd. 3
ミューテーションテスト ミューテーション解析 ご存知の方はいらっしゃいますか? © DeNA Co., Ltd. 4
Mutation Testing とは何か ● テストの品質を測定する手法 (ミューテーション解析とも呼ぶ) ● コードに意図的な不具合(ミュータント)を入れて、テストが失敗するかを見る。 ○ 🙆失敗すれば killed ○ 🙅失敗しなければ survived ● Coverageレベルで確認できるのはテストコードがそこを通ったか。 ● Mutation Testingでは、そのテストが不具合を発見できる能力があるかどうかを確認 できる。 © DeNA Co., Ltd. 5
シンプルな例 [IMAGE: IMG_015] Replace this with the actual image from Drive © DeNA Co., Ltd. 6
Test実行 (日本では)成人は18歳からなので、実装にはバグがあるにもかかわらず Testがpassしてしまう *Coverageは100% © DeNA Co., Ltd. 7
Mutation Testingの実施 ● > を >= に変える mutant を適用してtest実行する *意図的な不具合を混入する ● Test実行 => Testが落ちない(Survived) Testが十分では無いことが機械的にわかる © DeNA Co., Ltd. 8
Mutation Testingの基本的な考え方 ● Test対象コードに変異(mutant)を埋め込み、Test Suiteを回す ● Test Suiteがfailしなかった場合、Test Suiteにはその変異を発見できる能力がない、 つまり、適切なTest Suiteになっていないと捉える ● 変異は様々な種類がある ○ Operatorの書き換え + → - / < → <= … ○ literalの書き換え 1 → -1 / “some string” → “” ○ ● 関数呼び出しの削除 https://pitest.org/quickstart/mutators/ あたりが参考になる © DeNA Co., Ltd. 9
もう一つの例 Query Builder © DeNA Co., Ltd. 10
このTest Codeは十分か? © DeNA Co., Ltd. 11
このTest Codeは十分か? ←Test Data作成部分 © DeNA Co., Ltd. 12
Server Sideプログラミングあるある ● 複雑なSQLのTestにはDBに十分にTest Dataを入れておかなくてはならない ● どれくらいのデータを入れておくと十分なのか、なかなか判断しづらい ● (個人の経験) 100行超えるSQLに対するTestをレビューするのは無理... © DeNA Co., Ltd. 13
Query Builderに対するMutation (仮説) Queryの条件を書き換えてもTestが通る ⇨ 条件の部分をTestできる能力がない © DeNA Co., Ltd. 14
Mutation Testingでわかる可能性があること ● Test Data / Fixtureの網羅性 ● Assertionが正しいか ● ライブラリの挙動を勘違いしていないか ○ ● Equalsの振る舞いの誤解など etc... AI AgentがTestを大量に生成する時代、Testの”正しさ”を計測できる手法になりうる © DeNA Co., Ltd. 15
今日の結論: Mutation Testingをやろう © DeNA Co., Ltd. 16
現実の壁 ● 運用が難しい ● とんでもなく実行時間がかかる © DeNA Co., Ltd. ○ mutant1つごとにTest Suiteを回すのがmutation testingの基本的な挙動 ○ mutantを100個埋め込むとTest実行時間100倍 ○ 何も考えずにやると、大体数万個のmutantを埋め込む ○ 厳しい 17
rmut ● 自作のMutation Testing Toolを作っている ○ DeNAの実プロダクトで運用にチャレンジ中 ● 高速化・運用上の工夫を時間が許す限り話します ● Runtime MUTation © DeNA Co., Ltd. 18
Mutation Testing Toolの素朴な実装 実装は簡単 実行速度は... © DeNA Co., Ltd. 19
AST書き換え & Compileを1回で済ますアイディア こうする これを *説明のために簡略化しています。 実際の実装はもう少し複雑です。 © DeNA Co., Ltd. 20
AST書き換え & Compileを1回で済ますアイディア ● 1回のAST書き換えで 全ての ミューテーションを埋め込み、TestIdを付与する ● TestMainでTestをloopさせ、loopごとにUniqueなTestIdをSetして、それぞれのloopで 異なるMutatorが起動するようにする ● AST書き換え & Compileが1度で済む はやい!!!! © DeNA Co., Ltd. *rmutオリジナルのアイディアというわけではありません。他のmutation testing toolもこの方法で実装され ているものもあります。 21
一旦まとめ ● この設計だけで実行時間の問題が全て解決!とはならない ● (そもそも実装が難しい) ● Mutationの結果無限ループを発生させてしまう問題など 実運用にはまだまだ課題がある ● callgraph解析で効率的にmutation範囲を絞るなどの運用上 の工夫もしています ● この辺りの問題を効率よくテストするための手法について Lint Night #4( https://lintnight.connpass.com/event/385142/ ) で話す予定です © DeNA Co., Ltd. 22
Appendix. © DeNA Co., Ltd. 23
実装の難しさ ● 実装が難しい ● 素朴な実装だと、Compile Errorになるような書き換えを行なっても、そのmutantだ けが失敗(killed)とマークされる ● 今回の実装だとAST書き換えにおいてComile Errorになるような書き換えを行なって しまったら、全てのMutation Testingが動かない。 © DeNA Co., Ltd. 24
Compileができるコードを生成するのが難しい理由 ● 匿名関数でmutatorをwrapするという特性上、書き換え対象のASTが何の型なのかを 知る必要がある ○ ● © DeNA Co., Ltd. ASTだけではわからないので、packages.Loadを使っている この場合、 a + b というexpressionが何の型を持つのかを調べる必要がある 25
注意を要するケース Untyped ● untyped int のケース ● 1000 というリテラルだけをみても型が ● なにも考えないとこうなる 決まらないケースがあり、ASTの親の型 やコンテキストなどを調べる必要があ る。 © DeNA Co., Ltd. 26
Compile Errorは突破しても... ● Mutationによる無限ループ混入問題 ● 解決策 ○ Timeout ■ go標準の仕組みを使える ■ 何万件とtestを回す中で1回の test実行で数分もっていかれ るのは辛い ○ LoopCounterを仕込む © DeNA Co., Ltd. 27
LoopCounter ● for を発見したら、AST書き換え時にLoopCounterを仕込み、n回loopしたらpanicす るようにして、早期にtestを失敗させる ● 全てではないが、ある程度の無限ループには対応できるようになった。 ○ ● © DeNA Co., Ltd. goto や再帰により発生する無限ループなど、まだ対応できていないものもある それでも無限ループしてしまうものはgo testのtimeoutで対応する 28
無限ループ? ● 1万回LoopCounterを回すのに1万秒かかる... ● 未解決問題...? © DeNA Co., Ltd. 29
その他の運用上の工夫 ● sync.*系のmutationの除外(deadlockするので) ● mutation範囲の限定 ○ gitのdiffからcallgraphを解析して、そのPRでどのファイルが影響がありそうか を推定し、mutationをかける範囲を限定する ● custom mutator pluginの仕組み ○ squirrel等のGo標準以外のコードに対するmutatorはpluginで提供 & ユーザが pluginを書いて拡張できるようにする © DeNA Co., Ltd. 30
課題は色々... ● Compile Errorがたまにある ○ そんな構文が!?のような驚き ○ 構文解析・書き換えのツールの効率的なtest手法について Lint Night #4( https://lintnight.connpass.com/event/385142/ )で話す予定です。 ● どこまで行っても実行時間は安定しない ○ ● 急に無限ループを踏むことがある Survivedなmutationであっても意味がないことがある ■ ● © DeNA Co., Ltd. logger, 例外処理など plugin配布の難しさ ○ go/plugin... ○ wasiで配布も難しい 31
Mutation Testingについて知るためのreferences ● https://stryker-mutator.io/docs/ ● https://github.com/hcoles/pitest/blob/master/so_you_want_to_build_mutation_t esting_system.md ● © DeNA Co., Ltd. https://research.google/pubs/state-of-mutation-testing-at-google/ 32
まとめ ● Mutation Testingの紹介 ○ ● ● © DeNA Co., Ltd. Testの品質を測定しよう 自作Mutation Testing Tool, rmutの設計 ○ Compileを一度にすませ、test実行時にmutationを制御する設計 ○ 無限ループへの対応 ○ callgraph解析などで適用範囲を最小化する rmutの課題 ○ 実行時間 ○ plugin 33
© DeNA Co., Ltd. 34