はじめに
米田です。
この記事では、業務で使用しているJunit・JMockitについて、学んだことをまとめてみようと思います。
Junitとは?
Junitとは、Javaプログラムのテストをおこなうためのフレームワークです。
どんなエラーがどこで出たかなど、テストにおいて非常に便利なツールです。
実務では、単体テスト(UT)で活用しています。
単体テストの詳細な説明は省きますが、「ソース単位のテスト」くらいのイメージです。
JMockitとは?
モックを作成するためのフレームワークです。
単体テストでは、テスト対象のソース以外は参照しません。
そのため、テスト対象のソース内で呼んでいる別クラスのメソッドなどは、「モック」と呼ばれる身代わりを用意してテストを行います。
つまり、「Junitを使って単体テストをするときにJMockitを利用する」といったイメージです。
JMockitはJunitの拡張機能と思えばよいと思います。
テストでの基本的な使い方
ここでは、単体テストでの上記二つの使い方を簡単に記します。
なお、アノテーション付与などの細かな部分は省きますので、そちらはほかの方の記事をご参照いただければと思います。
Junitを利用した単体テスト
Junitでは、テスト対象の実行結果などを、想定結果と比較することができます。
これをアサーションチェックと呼び、テストソースではassertEquals() やassertThat() で実現します。
今回は、実務で利用しているassertThat()※ をもとに解説します。
個人的には、assertThat()のほうが直感的に条件を指定できるため、可読性が高くお勧めです。
※利用にはassertjライブラリのインポートが必要です。
import static org.assertj.core.api.Assertions.*;
例:
テスト対象ソース
public class Sample {
public int sum (int num) {
int sum = num + 10;
return sum;
}
}
Sampleクラスのテストソース
public class SampleTest {
//テスト対象クラス
@Tested
private Sample target = new Sample();
//sumメソッドのテストメソッド
@Test
public void sumTest001 () {
//テスト対象の実行
int result = target.sum(5);
//@@@
//実行結果が15である(== 処理結果が想定通りである)ことを確認
assertThat(result).isEqualTo(15);
}
}
このように記載することで、テスト対象メソッドの処理が想定通り行われているかを確認することができます。
テストコードに登場したisEqualTo() メソッドは、実行結果に対し期待値を指定して同一性 をみるメソッドです。
私の現場では同値性 をみるメソッドとして使っていて、同一性にはisSameAs() メソッドを利用していたのですが、これはフレームワークでそのように定義してくれていたようです。
インスタンスの同値性を見たい場合、isEqualToComparingFieldByField() を使用するようです。
jMockitを利用したモック作成
先述した通り、テスト対象ソース内で他クラスのメソッドを呼んでいる場合には、そのメソッドを 「モック化」 し、戻り値をあらかじめ指定してあげることでテスト実行が可能になります。
また、モック化したメソッドに渡された引数を確認したり、モック化したメソッドが何回呼ばれたかを確認したりといったこともできます。
例:
テスト対象ソース
public class Sample {
//テスト対象メソッド
public int sum (int num) {
//別クラスメソッド呼び出し
int sum = Other.sumPlus(num);
return sum;
}
}
//別クラス
public class Other {
//テスト対象から呼ばれている別クラスメソッド
public static int sumPlus (int num) {
int sum = num + 1000;
return sum;
}
}
Sampleクラスのテストソース
public class SampleTest {
//テスト対象クラス
@Tested
private Sample target = new Sample();
//sumメソッドのテストメソッド
@Test
public void sumTest001 () {
//モック定義と戻り値設定
new Expectations(){
//OtherクラスのsumPlusメソッドをモック化
Other.sumPlus(anyInt);
//sumPlusメソッドが呼ばれた時の戻り値設定
result = 1005;
}
//テスト対象の実行
int result = target.sum(5);
//モック呼び出しの検証
new Verifications(){
int testParam = 0;
//モックに渡された引数取り出し
Other.sumPlus(testParam = withCapture());
//モックの呼び出し回数検証
times = 1;
//@@@
//モックに渡された引数が想定通りであることを確認
assertThat(testParam).isEqualTo(5);
//実行結果が1005である(== 処理結果が想定通りである)ことを確認
assertThat(result).isEqualTo(1005);
}
}
}
いろいろまとめて書いてしまったので、一つずつ説明します。
Expectations()
モック定義と返却値設定に利用するメソッド。
モック化
クラス名(インスタンス可).メソッド名(同一型) を宣言することで、当該メソッドをモック化します。
返却値設定
モック宣言の下にresult = 設定したい返却値 と記載することで、当該メソッドが呼び出された際の返却値を設定できます。
Delegate
コード例には記述していませんが、resultにDelegateを記載することで、当該メソッドが呼ばれた際に行う処理を記載することができます。
例
//モック定義と戻り値設定
new Expectations(){
//OtherクラスのsumPlusメソッドをモック化
Other.sumPlus(anyInt);
//sumPlusメソッドが呼ばれた時の処理設定
result = new Delegate<Integer>() {
public int mockDelegate() {
System.out.println("通過");
return 20000;
}
};
}
当該メソッドが呼ばれた際は以下の挙動をします。
1.mockDelegateを通過し、コンソールに「通過」を出力
2.mockDelegateが20000を返し、resultに代入(当該メソッドの戻り値となる)
3.テスト対象メソッドの後続処理が実行される
このように、テスト中に別処理を挟むことができるという利点があります。
(余談)
実務でも一度だけこのDelegateを使う機会がありました。
後述するwithCapture()で引数を取得し、実行後に渡された時点の引数をアサーションチェックするテストだったのですが、アサーションチェックでうまくいかず、、
よくよく調べると、取得した引数が参照渡しかつ後続処理で編集されていたため、呼ばれた段階で取得した引数の中身が最終的に変わってしまっていました。
こういった場面でDelegateを使うと、引数をディープコピーして退避したり、メソッド呼び出しの時点でアサーションチェックしたりといったことが可能になります。
Verifications()
モックの実行回数や引数を検証するメソッド。
withCapture()
当該メソッドに渡された引数をキャプチャする。
キャプチャした際変数に退避することで、渡された引数が想定通りか、等のテストが可能になります。
最も使うもののひとつです。
times
当該メソッドが呼び出された回数を確認するための要素。
たとえばtimes = 1を指定した場合、当該メソッドは1回呼び出されると想定していることになります。
テストの結果一回も呼ばれなかったり、逆に2回以上呼ばれなかったりした場合は実行時例外になります。
assertThat()
assertThatは、Verifications内でも記載することができます。
使う場面としては、withCaptureで取り出した引数をチェックしたい場合等です。
取り出した引数の格納先をVerifications内で宣言した場合、ローカル変数となるためVerifications内でしか使用できません。
そのため、その場でアサーションチェックをしてしまったほうが都合がよいです。
さいごに
こうして技術記事を書いてみると、実務でなんとなくつかっていたものを改めて理解できるのでとても勉強になると感じました。
余談ですが、この記事を書くまで、現場で使っているモックフレームワークがMockito だと勘違いしていました、、、、、、
Expectationsを検索しても出てこなかったのはそういうことだったのか、、、、