この記事の対象者
- 環境がPJ側ですでに用意されている
- JUnit未経験/JUnit初心者
- JUnitでテストをするってことは分かるけど、どうコーディングすればいいかわからない
あなたが考えるべき流れ
1. テスト対象のメソッドを理解しよう
例えばテスト対象のクラスがServiceImplだった場合。
ServiceImplには、呼ばれるべきpublicな メインメソッド があり、その中で実行される 外部メソッド や privateメソッド があるはず。
テスト観点は
"メインメソッド が 外部メソッド の結果に左右されず正常終了し、かつ、想定通りの異常終了をするか"
である。
なのでまずは動作を確認すべき メインメソッド がどれであるかを把握しよう。
2. 得たい結果を把握しよう
JSON、ResultCode、インスタンス...etc
テスト実行後にどのような結果が返れば終了か、文字列系であればどこかに控えておこう。
3. MOCK(モック)とは何か知ろう
手順1で述べたように、メインメソッドは外部メソッドの結果に左右されてはならない。
そこで、
"外部メソッドが呼ばれたら固定でこの値(結果)を返せ"
と命令を埋め込むのが MOCK の役割だ。
詳しい使い方は後述するが、仮想動作を設定する変数 と認識しておこう。
※スタブ化する、とも呼ばれる。
全体像を眺める
@RunWith(MockitoJUnitRunner.class)
public class TargetClassTest {
// 下準備開始
@Mock
private Mapper mockMapper;
@InjectMocks
private TargetTestServiceImpl service;
@BeforeEach
public void setup() {
// パターン1
MockitoAnnotations.openMocks(this);
// パターン2
ReflectionTestUtils.setField(service, "mapper", mockMapper);
}
// 下準備終了
// 以下テスト記述
@Test
@DisplayName("正常終了系")
public test001() throws Exception {
// 1
when(mockMapper).exeQuery(any()).thenReturn(anyString());
// 2
ResultCode actualResult = service.method();
// 3
assertEquals(ResultCode.RETURN_SUCCESS, actualResult);
}
}
テスト全体の構造
test001
メソッドに注目する。
番号順にざっくり言ってしまうと、1で下準備をし、2でメソッドを実行し、3で結果を比較している。
それらを順に解説していく。
1. 下準備
when(mockMapper).exeQuery(any()).thenReturn(anyString());
上記のコードではメソッドチェーンにより3つのメソッドが呼ばれ、
when(この変数で)
exeQuery(引数を問わずexeQueryが呼ばれたら)
thenReturn(何らかのStringを返す)
という処理が行われている。
再三の説明になるが、外部のメソッドに結果が左右されてはならないので、exeQueryメソッドは引数も返り値も仮の物を用意すればいい。
モック変数を利用するには以下の手順が必要だ。
- @Mockで外部メソッドが定義されているクラスで変数を用意する。
- @InjectMocksでテスト対象となるクラス(ここではサービスクラス)変数を用意する。
-
MockitoAnnotations.openMocks(this)※1
というおまじないでテスト対象クラスにモックを注入する。
変数を用意しおまじないを書くだけで良いので非常に簡単だ。
ちなみに、おまじない自体はtest001()
に記述しても良い。
だがその場合にはtest002,003...と増えた場合に毎度記述しなければならないので、面倒だし美しくない。
そこで@BeforeEachが登場する。
このアノテーションが付与されたメソッドは TargetClassTestクラス内の@Testが付いたメソッドが実行される前に毎回実行される 様になり、全てのテストメソッドに記述しなければならないような共通下準備をまとめておくことが出来る。
※1 おまじないのパターンについて
テスト対象クラスのDIが@Autowiredの場合にはパターン1。
テスト対象クラスのDIがコンストラクタインジェクションの場合にはパターン2。
2. テストメソッドの実行
ResultCode actualResult = service.method();
ここでは非常にシンプルだ。
メソッドの返り値を受け取れるクラスの変数を用意し、@InjectMocksが付与されたクラスのテストしたいメソッドを実行する。
終わり。
3. 結果の確認
assertEquals(ResultCode.RETURN_SUCCESS, actualResult);
assertEqualsメソッドは引数1と2を比較し、一致すれば正常終了するようになっている。
メソッドとしては引数のどちらがexpected(期待値)でどちらがactual(実行結果)であるかは問われない。
しかし可読性や慣例として、引数1がexpectedであるので、参画しているPJが逆でない限りは従おう。
テストの実行
Eclipseであれば作成したクラスを右クリックし、コンテキストからカバレッジまたはテストの項目、JUNITを選んで実行すればJUNITビューに結果が出力される。
上記のサンプルから拡張する
-
any()
メソッド
例えばany()
メソッドの種類も沢山ある。any()
はどこにでも入れられる反面、明確に型を提示しなければならないメソッドではanyLong()
やanyString()
としてやる必要がある。 -
thenReturn()
メソッド
このメソッドは「この返り値を返せ」というものだが、thenThrow(new Exception("test"))という「Exceptionを返せ」というものがある。
ちなみに、下記の様にdoNothing()
を使用すれば「なにもするな」とも記述できる。
doNothing().when(mockMapper).exeQuery(any());
-
assert
結果の比較も沢山ある。
使用するJunitのバージョンによりassertThatだったりassertEqualsだったりする。
比較せずassertTrueで真が返っているかと比較することも出来る。
また、以下のように「このメソッドは1回しか呼ばれていないはず」とテストするパターンもある事も覚えておこう。
verify(service, times(1)).exeQuery(any());
このように、どのような動作をモック(スタブ)したいのか、どのような結果を確認したいのか、テストの目的をあらかじめ把握することで「JUNIT iterator MOCK」などと検索するワードが浮かぶだろう。
エラーが起きたら
テスト対象クラスの構成や、ラムダ式をMOCKしようとしていたり(ほぼ不可能)、エラーの原因特定は難しい。
確認点としては、
- デバッグで1行ずつ停止し、外部メソッドを使用する変数に「MOCKITO~」などが代入されているか
→されていなければMOCKの注入が失敗している。@MOCKではなく@SPYが必要だったりする。大人しく先輩らに「MOCKが上手く注入できないのですが……」と聞いてしまうのも手だ。 - 外部メソッドが想定した仮想値を返さない
→この場合はテスト対象クラスの動作とMOCKが想定した動作がマッチせず、MOCK動作でなく外部メソッドが実際に動作してしまっていることが考えられる。any()
の指定または、厳密な変数(文字列や数値)をMOCKで指定してみよう。
などが挙げられる。
テスト対象クラスが複雑であるほどMOCKが難しくなるし、あなたがテスト対象クラスの制作者でなければなおさら難しい。
そして何よりも重要なことが JUNITでテストされることを想定しコーディングしているか である。
あまりにも上手くJUNITがコーディング出来ない場合はあなたの責任でない場合も少なくない。
それをあらかじめ心に留めておくことで心の負担は軽くなるだろう。
終わりに
テストの考え方を本記事で理解できれば他の記事を参考にするといい。
下準備して、実行して、確認する。このように考えると非常にシンプルに作れるので、難しく考えずトライしてみよう。