はじめに
PITを使用してKotlinなAndroidプロジェクトでMutation Testingを導入するまでのメモです。
Mutation Testing
テストコードが正しいかを計測するために、Mutant Testingという手法があります。
Mutant Testingではプロダクトコードを機械的に変更し、変更されたコードに対してテストを実行します。そしてテストが失敗するかを確認することで、プロダクトコードの振る舞いの変更をテストコードが検知できるかをチェックする手法です。
PIT
今回はMutation TestingツールとしてPITを紹介します。PITはJavaとその他JVM言語用のMutation Testingツールです。検索するときはpitest
で検索すると良いです。
PITの素晴らしい点は、3rdパーティー製Android用Gradle Pluginが公開されており、導入が容易なところです。
その他Java向けMutataion Testingツールとの比較は下記にまとまっていました。PITのドキュメントです。
こちらのページでは、どのようなツールがあるかとそれぞれどのような設計で実装されているかが記載されています。
Java Mutation Testing Systems
Android + PIT
AndroidプロジェクトでPITを実行できるようにするために、Android版gradle-pitest-pluginを使用します。
GitHub - koral--/gradle-pitest-plugin: Gradle plugin for PIT Mutation Testing in Android projects
Pluginの追加
buildscript {
..
dependencies {
..
classpath 'pl.droidsonroids.gradle:gradle-pitest-plugin:0.1.7'
}
}
apply plugin: 'pl.droidsonroids.pitest'
もともとgradle-pitest-pluginがあり、Android版gradle-pitest-pluginではそのpluginをAndroidで使えるように修正したものです。オプションについては元のplguinのページを参照します。
Gradle plugin for PIT Mutation Testing | gradle-pitest-plugin
実行をするのに最低限必要な設定は下記でした。
pitest {
targetClasses = ['my.package'] //テストしたいpackage
}
実行
下記コードに対してPITを実行します。
fun plusNumberChecker(number: Int): String {
if (number < 0) {
return "NG"
}
return "OK"
}
@Test
fun plusNumberChecker_NG() {
assertThat(plusNumberChecker(-1), equalTo("NG"))
}
@Test
fun plusNumberChecker_OK() {
assertThat(plusNumberChecker(0), equalTo("OK"))
}
pitestタスクを実行します。
./gradlew pitest //すべてのVariantに対して実行
or
./gradlew pitest[Variant]
デフォルトの設定ですと、app/build/reports/pitest/variant/日時
にHtmlレポートが出力されます。
下記が実際に出力されたレポートです。
Mutations
にコードをどのように変更したかが記載されています。
PITではコードをどのように変更するかの定義をmutatorと呼んでいます。mutatorの一覧と詳細がMutation operatorsのページにまとまっているので、レポートと照らし合わせながら確認します。
Mutation operators
今回は以下のmutatorによってコードが変更されました。
- changed conditional boundary:
<
を<=
に変更 - negated conditional boundary:
<
を>=
に変更 - mutated return of Object value: 戻り値を
null
に変更 × 2
また、4回の変更すべてでテストが失敗したので(KILLED)、Mutation Coverageは100%(4/4)です。
試しに、テストコードを変更してplusNumberChecker_OK
のassertionを削除します。必ずパスするテストコードになりました。
@Test
fun plusNumberChecker_NG() {
assertThat(plusNumberChecker(-1), equalTo("NG"))
}
@Test
fun plusNumberChecker_OK() {
plusNumberChecker(0)
}
上記テストコードでPITを実行した結果のレポートです。
最初のレポートと比較すると、結果が以下のように変わっています。
- changed conditional boundaryをした(
<
を<=
に変更した)コードのテストがパス(SURVIVED) - mutated return of Object valueをした(戻り値をnullに変更した)コードのうち1つのテストがパス
- 4回の変更のうちテストが失敗したのは2件なので、Mutation Coverageは50%
Mutation Testingではこのように、プロダクトコードの変更を検知できないテストコードを見つけだすことができます。
Kotlinでの制約
さきほどのMutation Testingでは戻り値をnullに変更するmutatorが使われました。
ただ、plusNumberChecker
メソッドの戻り値はNon-Nullです。これでは振る舞いだけでなくIFまで変わってしまいます。
PITでは、Kotlinにおいてノイズになってしまうmutatorへの対応を下記issueで議論しています。
improve kotlin support · Issue #260 · hcoles/pitest · GitHub
こちらのissueによると、1.3.0でNonNullのアノテーションを考慮する新しいmutatorが複数実装されました。
それらのmutatorはデフォルトでは有効になっておらず、NEW_DEFAULTS
というmutator setに含まれているようなので、使用するmutator setを変更します。
pitest {
..
mutators = ["NEW_DEFAULTS"]
}
この設定でPITを実行したレポートが以下です。
Mutationsの項目の中で、mutated return of Object value
がreplaced return value with ""
に変わっています。名前からわかるとおり、戻り値を空文字にするmutatorに変更されました。
このように、PITではKotlinのサポートの開発も進めています。
pitest-kotlinというKotlinのpluginのプロジェクトもあるのですが、まだMaven Repositoryにあがっていないようでした。
GitHub - pitest/pitest-kotlin: Kotlin plugin for pitest
When the plugin is enabled pitest will avoid creating junk mutations in code that uses
・ de structuring code
・ null casts
・ safe casts
・ elvis operator
READMEには上記の記述もあり、今後のアップデートが気になるところです。
おわりに
KotlinなAndroidプロジェクトでPITを導入する手順を紹介しました。Gradle Pluginが公開されているおかげで導入は容易です。
一方Kotlinコードのmutatorはこれから改善されていく状態ですので、引き続き情報のアップデートをしていく必要がありそうです。