Android + Kotlin + PITでテストコードをテストする

# はじめに

PITを使用してKotlinなAndroidプロジェクトでMutation Testingを導入するまでのメモです。

# Mutation Testing

テストコードが正しいかを計測するために、Mutant Testingという手法があります。
Mutant Testingではプロダクトコードを機械的に変更し、変更されたコードに対してテストを実行します。そしてテストが失敗するかを確認することで、プロダクトコードの振る舞いの変更をテストコードが検知できるかをチェックする手法です。

[ミューテーション解析 - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%9F%E3%83%A5%E3%83%BC%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E8%A7%A3%E6%9E%90)

# PIT
今回はMutation Testingツールとして[PIT](http://pitest.org/)を紹介します。PITはJavaとその他JVM言語用のMutation Testingツールです。検索するときは`pitest`で検索すると良いです。
PITの素晴らしい点は、3rdパーティー製Android用Gradle Pluginが公開されており、導入が容易なところです。

その他Java向けMutataion Testingツールとの比較は下記にまとまっていました。PITのドキュメントです。
こちらのページでは、どのようなツールがあるかとそれぞれどのような設計で実装されているかが記載されています。
[Java Mutation Testing Systems](http://pitest.org/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](https://github.com/koral--/gradle-pitest-plugin)

## Pluginの追加

```build.gradle
buildscript {
    ..
    dependencies {
        ..
        classpath 'pl.droidsonroids.gradle:gradle-pitest-plugin:0.1.7'
    }
}
```

```app/build.gradle
apply plugin: 'pl.droidsonroids.pitest'

```

もともとgradle-pitest-pluginがあり、Android版gradle-pitest-pluginではそのpluginをAndroidで使えるように修正したものです。オプションについては元のplguinのページを参照します。
[Gradle plugin for PIT Mutation Testing | gradle-pitest-plugin](https://gradle-pitest-plugin.solidsoft.info/)

実行をするのに最低限必要な設定は下記でした。

```app/build.gradle
pitest {
    targetClasses = ['my.package'] //テストしたいpackage
}
```

## 実行

下記コードに対してPITを実行します。

```kotlin
fun plusNumberChecker(number: Int): String {
    if (number < 0) {
        return "NG"
    }
    return "OK"
}
```

```kotlin
@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レポートが出力されます。

下記が実際に出力されたレポートです。

<img width="594" alt="スクリーンショット 2018-10-12 2.27.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/279160/263878d7-59ba-db4f-dc19-5683dd90e4d5.png">

<img width="1375" alt="スクリーンショット 2018-10-12 2.18.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/279160/51de6056-920a-2760-6fbc-15394aef6d86.png">

`Mutations`にコードをどのように変更したかが記載されています。
PITではコードをどのように変更するかの定義をmutatorと呼んでいます。mutatorの一覧と詳細がMutation operatorsのページにまとまっているので、レポートと照らし合わせながら確認します。
[Mutation operators](http://pitest.org/quickstart/mutators)

今回は以下のmutatorによってコードが変更されました。

- changed conditional boundary: `<`を`<=`に変更
- negated conditional boundary: `<`を`>=`に変更
- mutated return of Object value: 戻り値を`null`に変更 × 2


また、4回の変更すべてでテストが失敗したので(KILLED)、Mutation Coverageは100%(4/4)です。

試しに、テストコードを変更して`plusNumberChecker_OK`のassertionを削除します。必ずパスするテストコードになりました。

```kotlin
@Test
fun plusNumberChecker_NG() {
    assertThat(plusNumberChecker(-1), equalTo("NG"))
}

@Test
fun plusNumberChecker_OK() {
    plusNumberChecker(0)
}
```
上記テストコードでPITを実行した結果のレポートです。


<img width="611" alt="スクリーンショット 2018-10-12 2.26.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/279160/417a7d98-0721-4a8c-81ec-8d6d74ba7aa1.png">

<img width="1394" alt="スクリーンショット 2018-10-12 2.27.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/279160/a840a9a4-81c5-0265-6d1f-c10661230930.png">

最初のレポートと比較すると、結果が以下のように変わっています。

- 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](https://github.com/hcoles/pitest/issues/260)

こちらのissueによると、1.3.0でNonNullのアノテーションを考慮する新しいmutatorが複数実装されました。
それらのmutatorはデフォルトでは有効になっておらず、`NEW_DEFAULTS`というmutator setに含まれているようなので、使用するmutator setを変更します。

```app/build.gradle
pitest {
    ..
    mutators = ["NEW_DEFAULTS"]
}
```

この設定でPITを実行したレポートが以下です。

<img width="932" alt="スクリーンショット 2018-10-12 2.55.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/279160/453124ce-e685-dff5-6803-701caef2902a.png">


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](https://github.com/pitest/pitest-kotlin)

```
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はこれから改善されていく状態ですので、引き続き情報のアップデートをしていく必要がありそうです。