LoginSignup
3
2

Junit5,Mockitoの実装ポイントメモ集

Posted at

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個使っている点に注目してください。

src/main.java配下
    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になることはありません。

src/test/java配下
    @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メソッドについてもモックの挙動指定をする必要があります。

src/test/java配下
    @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する

(工事中)

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2