Javaのユニットテストを書くとき、普段は例外が発生するパターンのテストを例外ごとに書き、それとは別に正常終了するテストを書いて、パラメータと結果の組み合わせをfixtureで渡している。
新しくアサインされたJavaプロジェクトでテストを書こうとしたら、テスト対象のメソッドはDAOによるデータ取得とビジネスロジックが交互に行われているトランザクションスクリプトで、DAOをモック化しても大量の前処理が必要だった。
テストフレームワークはSpockを使っているが、Groovyで書いても前処理だけで60行ほど使ってしまうので、成功と例外をまとめて同じテストメソッドで書けないかと思い、試してみたらスマートではないがなんとかできたのでメモ。
状況
前処理で大量にDAOをモック化し、検索結果として取得するインスタンスを用意しないといけない。
省略しているが、既存のテストメソッドでDAOなどを利用しているため、 setup
メソッドでのモック化や @Shared
での共有もやりづらく、追加したテストメソッド内で完結させたい。
def targetClass = new TargetClass() // DAOのインスタンス生成 @Unroll def "targetMethodの例外発生テスト"() { given: // DAOのモック化、取得するインスタンス生成など when: targetClass.targetMethod(...) then: def e = thrown() e.class == ... e.message == ... } @Unroll def "targetMethod(#param) == #expected のテスト"() { given: // DAOのモック化、取得するインスタンス生成など // テストのパターンによってインスタンスをいじりたい expect: targetClass.targetMethod(param) where: param || expected ... }
対応
where
で指定するパラメーターに例外クラスを含めておき、 then
で thrown
で例外を取得。パラメーターで例外が指定されている場合は例外を比較、 null
なり _
なりで有効なクラスが指定されていなければメソッド実行結果と期待値を比較、といった判定ができないかと思ったが、 thrown
で例外クラスの指定が必要だったため難しかった。
また、 thrown
は then
の先頭で書かないとエラーになるため、例外クラスを where
で書いて判定することもできなかった。
仕方ないので、 when
と then
を複数記述し、成功時とエラー時で処理を切り分けることとした。 where
で指定したパラメーター数だけ例外発生のテストが重複して実行されてしまうが、ひとまず目的は達成できた。
@Unroll def "targetMethod(#param)のテスト"() { given: // DAOのモック化、取得するインスタンス生成など def errorParam = ... // エラーが発生するパラメーター when: def actual = targetClass.targetMethod(param) then: actual == expected when: targetClass.targetMethod(errorParam) then: Exception e = thrown() e.message == errorMessage where: param || expected ... || ... }
振り返り
既存のソースにテストを追加する形となったため、かなり無理矢理になってしまった。やはりテストを書きやすいよう、データの扱いとビジネスロジックは分離しておくべきだなぁ。
where
に例外クラスやメッセージを追加することで、もう少しうまくまとめられるかもしれない。そもそももっとうまいやり方があるかもしれないが、とりあえず目的達成はできたのでここまで。テストに時間をかけすぎるのも本質的ではないし。