JUnit4とJUnit5のimport文の違い
現在、多くのシステム開発現場で用いられているのはJUnit5です。
import文が異なるため、注意しましょう。
JUnit4の場合は以下です。
import org.junit.Test;
import org.junit.Before;
import org.junit.After;
import org.junit.Assert;
JUnit5の場合は、org.junit.jupiter.apiパッケージを中心に使用します。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
これに伴い、全体的に使用するアノテーションも大きく異なります。
JUnit4 | JUnit5 |
---|---|
@Before | @BeforeEach |
@After | @AfterEach |
@BeforeClass | @BeforeAll |
@AfterClass | @AfterAll |
@Ignore | @Disabled |
@Category | @Tag |
テストメソッドのアクセス修飾子
テストメソッドのアクセス修飾子がprivateだと、そのテストメソッドは実行されません。
protectedでも動きますが、通常はpublicか、アクセス修飾子をつけない(package-private)にすることが多いでしょう。
実行前と実行後のテンプレ
とりあえずこの様な共通処理を親クラスとして継承しておくと、テストケースを一度にたくさん実行した時の確認がラクでしょう。
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
public class TestParent {
@BeforeEach
public void before(TestInfo testInfo){
System.out.println("テスト開始------------------ " + testInfo.getTestMethod().get().getName());
}
@AfterEach
public void after(TestInfo testInfo){
System.out.println("テスト終了------------------ " + testInfo.getTestMethod().get().getName());
}
}
@Disabledの中には実施方法や理由を書いておくと親切
テストコードは、基本的には手作業での事前準備なしにそのまま実行して通るようにしておくのが理想です。
しかし、全部が全部そのようなつくりになっているかというとそうではないのが現実です。特にテスト工程は工数が短い傾向があり、突貫工事になりがちだと思います。
特にprivateメソッドのモック化は難しく、例えばprivateメソッド内のcatchブロック内のさらにcatchブロックに入る分岐を実現するのは骨が折れる作業です。
その様な場合は、テストメソッドに@Disabledをつけた上で、括弧内に実施方法、あるいはそのままでは実行できない理由を書いてからGitやSVNリポジトリにコミットしておくと親切ではないかと思います。
そうしていかないと、他の人が後日コード変更をしてテストコードが通らなかった場合、プロダクトコードに問題があるのか、条件に問題があるのかの切り分けが簡単にはつかなくなります。ゆくゆくメンテされずに見捨てられるテストメソッド(テストクラス)になりかねません。
@Test
@Disabled("デバッグ実行し、301行目でブレイクポイントを張って例外を発生させる")
public void testCase1(){
// テスト内容
}
}
また、JUnitには、条件付き実行アノテーションが多く用意されています。これも有効活用することで保守性の高いテストコードを書きましょう。
条件付き実行アノテーション
(工事中)
MockStaticについて
mockitoのバージョン3.4.0以上では、staticメソッドをモック化することができます。
staticメソッドをモック化する方法
以下は、現在時刻取得のLocalDateTime.now()をモック化する例です。(テストクラスの記述は割愛)
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import static org.mockito.Mockito.mockStatic;
// クラスの記述(割愛)
// テストメソッド
@Test
public void executeTest() {
LocalDateTime mockNow = LocalDateTime.of(2030, 01, 31, 12, 30);
try(MockedStatic<LocalDateTime> mocked = mockStatic(LocalDateTime.class);){
mocked.when(LocalDateTime::now).thenReturn(mockNow);
// 呼び出し
}
}
MockedStaticは処理が終わったらクローズする必要があります。
閉じないとどうなるかは、こちらの記事を参照ください。
MockedStaticの親の親はAutoCloseableインタフェースですので、try-with-resources構文が使えます。
Mockstaticでは部分的なモック化はできない
インスタンスメソッドに対しての@Spyとは異なり、Mockstaticでは部分的なモック化はサポートされていません。
例えば、以下のコードがあるとします。Filesのstaticメソッドを、existsとdeleteの2個使っている点に注目してください。
public void delete() {
Path path = Paths.get("src/test/resources/sample.txt");
if (Files.exists(path)) {
try{
Files.delete(path);
}catch(IOException e){
System.out.println("IOExceptionが発生しました。");
}
}
}
このテストケースとして、"IOExceptionが発生しました。"を出力させたいとします。
この時、以下の様に書くと、existsメソッドもモック化されてしまうため、deleteメソッドの「if (Files.exists(path)) {」でtrueになることはありません。
@Test
public void deleteTest() throws IOException{
File file = new File("src/test/resources/sample.txt");
file.createNewFile();
try(MockedStatic<Files> mocked = mockStatic(Files.class);){
mocked.when(() -> Files.delete(any())).thenThrow(new IOException());
new Sample0301().delete();
}
}
staticメソッドはこの様に、インスタンスメソッドに対しての@Spyの様に部分的なモック化はできません。
existsメソッドについてもモックの挙動指定をする必要があります。
@Test
public void deleteTest() {
try(MockedStatic<Files> mocked = mockStatic(Files.class);){
mocked.when(() -> Files.exists(any())).thenReturn(true);
mocked.when(() -> Files.delete(any())).thenThrow(new IOException());
new Sample0301().delete();
}
}
IOExceptionが発生しました。
DBUnitを導入する
DBUnitとは、データベースに依存するクラスのテストを行うためのJUnit拡張フレームワークです。
Excelファイルなどを入力ファイルとして、データベースのレコードを書き換えることができます。
メリットとしては、データベースアクセスを伴うプロダクトコードの単体テストを行うときに、条件となるテーブルのレコード状態をテストコードによってつくることができるため、テストの再現性、保守性が高くなります。
詳しくはこちらの記事をご参照ください。
ログ出力内容をassertする
(工事中)