自動テスト作成時に気をつけようと思ったことのメモ
※個人ブログ閉鎖のため記事をZennに移してみました。
ガチの「自動テスト論」ではなく、
クソ雑魚プログラマーのしょぼい自戒的なメモです🥺🥺
テストコードを書くときの観点
期待する動作を明確にする
大抵のテストは、「入力に対して出力された結果」を検証します。
似たようなテストのコピペではなくて、
自分が入れ込んだ処理がどんな動きをするのか?
明確にしてチェックしましょう。
バリエーションをつける
「期待する動作」 といって正常系の1パターンしか作らないと、バグの検出漏れが起きます。
- 正常系(正しい入力に対して正しい結果を返すことを確認する)
- 準正常系(想定内の異常値の入力に対して、正しくエラー処理をすることを確認する)
- 異常系(例外発生や、ハンドリングできない想定外のエラーに対する動作を確認する)
という切り口に従うとテストケースを挙げやすいです。
さらにそれらの中で取り扱う値自体にもバリエーションをつけられます(閾値チェックとか)。
絶対にAssertを実行するコードを書く
自動テストはプロダクトコードとは違って、ちゃんと失敗することが重要です。
自動テストにはArrange(準備)、Act(実行)、Assert(検証)という3段階がありますが、
条件文などが挟まって、何かの拍子でAssertが実行されなかった場合、
「Assertでエラーが返されなかった=テスト成功」 として扱われてしまいます。
例えば、 List を戻り値で返す関数をテストしているとします。
戻り値を検証するために、決まった要素にアクセスするコードを毎度書くのが面倒なので、
以下のようにlet
などのコードブロックで囲んで、その中でAssertするとします。
val resultList: List<Person> = class.method()
resultList.first { it.id == 1 }.let { person ->
assertThat(person.name).isEqualTo("佐藤")
assertThat(person.age).isEqualTo(26)
assertThat(person.job).isEqualTo("神")
}
resultList.first { it.id == 2 }.let { person ->
assertThat(person.name).isEqualTo("鈴木")
assertThat(person.age).isEqualTo(28)
assertThat(person.job).isEqualTo("プロニート")
}
すると、そもそもListのidに期待した値が存在しない場合、
let内のAssertが実行されることなくテストが終了します。
この時、なんとテストは「成功」という結果を表示します。バグの発見を遅らせます。気をつけよう。
テストコードを作成するタイミング
なるべく後回しにしない
テストコードを書くのは地味に時間がかかります。
かといって「自動テストは別ブランチで作るかぁ」と後回しにするのは良くないです。
クロスレビュー時に、軽微なミスが出てきたらめっちゃ恥ずかしいです。
そもそもバグを早期検出する仕組みとして自動テストが存在するので、
「変更を加えたソースコード + その動作を担保する自動テスト」をセットで用意するべきだと思います。
成果物にきちんと責任を持とう。
その他メモ
個人の作業環境でやりがちなミスのメモです。
書いたテストがなぜか全部失敗する
アホすぎて恥ずかしい。
大抵、ビルドオプションの指定がテスト用になっていない。
ビルドオプションの指定でアクセスするサーバーや、
実データ or モック用のクラスを参照するかを切り替えているプロジェクトでやりがち。
MockitoでInvalidUseOfMatchersExceptionが発生
テスト対象の関数内でコールしている別の関数をモック化する時、
Mockito.`when`(class.method(Mockito.anyInt())).thenReturn()
などとちゃんと書いているつもりなのにInvalidUseOfMatchersExceptionで失敗するやつ。
大抵、以下のパターン 。
①必須パラメータだけMockito.anyInt()
などと指定していて、省略可能パラメータについて指定していない
②Nullableの引数にMockito.anyInt()
などと指定している。IntじゃなくてNullが入るパターンも含めてany()
と書くといい。
あとなんか思い出したら更新するかも。
Discussion