LoginSignup
15
15

More than 1 year has passed since last update.

初めてのJUnit5単体テスト、最低限知っておきたいあれこれ

Last updated at Posted at 2022-10-04

:writing_hand_tone1:初めてjavaでテストコードを書くという経験をし、わからないことだらけで
書き方から分からず大変だったので自分の備忘録や復習のため、記事を書きました。
同じような方の少しでも参考になればと思います。

※必要なプラグイン、設定、importは記載していません。

開発環境: 
intellij / java17 / junit5

テストの実行方法

スクリーンショット 2022-09-29 22.19.49.png
左の▶︎のマークを押してテストの実行が可能

image.png
テストに成功した場合、緑の✅が表示されます。

アサーション

CalculatorApp.java

// テスト対象のクラス
public class CalculatorApp {

    // 除算メソッド
    public int testDivision(int dividend ,int divisor) {
        return dividend/divisor;
    }
}
CalculatorAppTest.java

// テスト実装クラス
class CalculatorAppTest {

    //テストメソッド
    @Test
    @DisplayName("除算メソッドに割り切れる整数が引数で渡された時の検証")
    void testDivision() {
        var calculator = new CalculatorApp();
        var result = calculator.testDivision(4,2);
        // アサーション
        assertEquals(2,result);
    }
}

テストしたいメソッドが正常に動作するかどうか、および正しい結果を返すかどうかを確認するために、assertを使用する。与えられた入力パラメータで条件を評価する。

アサーションの種類

テストメソッド内部の構造化、パターン

AAA(トリプルエー)

テストコードを読みやすくするための指針です。AAA は arrange, act, assert の略語で、arrange は『準備』、act は『実行』、assertは『検証』を表します。

  • Arrange
    • テストメソッドを呼び出すためのインスタンス生成、入力パラメータの準備
  • Act
    • テスト中のメソッドを呼び出し、実際の結果を返す
  • Assert
    • 保持された値を検証する
CalculatorAppTest.java

class CalculatorAppTest {

    @Test
    @DisplayName("除算メソッドに割り切れる整数が引数で渡された時の検証")
    void testDivision() {

        // Arrange テストメソッドを呼び出すためのインスタンス生成、入力パラメータの準備
        var calculator = new CalculatorApp();
        var dividend = 4;
        var divisor = 2;
        var expectedResult = 2;

        // Act テスト中のメソッドを呼び出し、実際の結果を返す
        var actualResult = calculator.testDivision(dividend,divisor);

        // Assert 保持された値を検証する
        assertEquals(expectedResult,actualResult);
    }
}

テストライフサイクル

※数字は実行される順を示しています。
@BeforeAll (1).png

  • BeforeAll
    全てのテストメソッドの前にメソッドを1回だけ実行させる

  • AfterAll
    全てのテストメソッド完了後にのみ1回だけ実行される

  • BeforeEach
    付与されているメソッドが各テストの実行前に実行される

  • AfterEach
    付与されているメソッドが各テストの完了後に実行される

この中でもよく使用するのは@BeforeEach

例外アサーション

CalculatorAppTest.java

class CalculatorAppTest {

    CalculatorApp calculator;

    @Test
    @DisplayName("除算メソッドに割り切れない整数が引数で渡された時、例外がスローされる検証")
    void testDivision_error() {

        calculator = new CalculatorApp();

        // Arrange
        var dividend = 4;
        var divisor = 0;
        var exceptionMessage = "/ by zero";

        // Act & Assert
        // 例外が発生することを確認する例外アサート
        var arithmeticException = assertThrows(ArithmeticException.class, () -> {
            // Act
            calculator.testDivision(dividend,divisor);
        });

        // Assert
        assertEquals(exceptionMessage, arithmeticException.getMessage());
    }
}

assertThrowsの第1引数で「発生するであろう例外」のクラスを指定し、第2引数(ラムダ式)でテスト対象の処理を実行する。

モックオブジェクトの作成

UserServiceTest.java

@ExtendWith(MockitoExtension.class) // このクラスでMockitoアノテーションを使えるようになる
public class UserServiceTest {
    // 処理省略
}

@ExtendWith(MockitoExtension.class)
このクラスでMockitoアノテーションを使えるようになる

UserServiceTest.java

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

    @InjectMocks
    UserService userService;

    @Mock
    UsersRepository userRepository;

    // 処理省略
}

@Mock
モック化するクラスにつける
@InjectMocks
テスト対象クラスにモックを注入

UserServiceTest.java

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

    // 上記の処理省略
    @DisplayName("ユーザー作成の検証")
    @Test
    void testCreateUser() {

    // Arrange
    Mockito.when(usersRepository.save(any(User.class)).thenReturn(true);

    // Act
    var user = userService.createUser(firstName, lastName, email, password, repeatPassword)

    // Assert
    assertNotNull(user);
    assertEquals(firstName, user.getFirstName());
    assertEquals(lastName, user.getLastName());
    assertEquals(email, user.getEmail());
    assertNotNull(user.getId());
    verify(usersRepository, Mockito.times(1)).save(any(User.class));
    }
}

モック化したクラスのメソッドを実行したときの戻り値を設定する

  • 書き方は2パターン
    • when(モックインスタンス.メソッド(引数)).thenReturn(戻り値);
    • doReturn(戻り値).when(モックインスタンス).メソッド(引数);

メソッド呼び出しの検証

UserServiceTest.java

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

// 上記の処理省略
    @Test
    @DisplayName("ユーザー作成の検証")
    void testCreateUser() {

    // Arrange
    // モック化したクラスのメソッドを実行したときの戻り値を設定する
    // Mockito.when(モックインスタンス.メソッド(引数)).thenReturn(戻り値)
    Mockito.when(usersRepository.save(any(User.class)).thenReturn(true);

    // Act
    var user = userService.createUser(firstName, lastName, email, password, repeatPassword)

    // Assert
    // メソッド呼び出しの検証
    verify(usersRepository, times(1)).save(any(User.class));
    // 一度もメソッド呼び出しがされない検証。never()はtimes(0)と同じ
    // verify(usersRepository, never()).save(any(User.class));

メソッドが呼ばれた回数をテストする
verify(モックオブジェクト, times(回数)).テストするメソッド(引数);

JPAエンティティのテスト

UserEntityTest.java

@DataJpaTest
public class UserEntityTest {

    // エンティティマネージャの代替オブジェクト、データ層をテストするときのクラス
    // 情報を永続化し、すぐにデータベーステーブルと同期させることができる
    @Autowired
    private TestEntityManager testEntityManager;

    @Test
    void userEntitytest() {
        // Arrange
        var userEntity = new UserEntity();
        userEntity.setUserId(UUID.randomUUID().toString());
        userEntity.setFirstName("yamada");
        userEntity.setLastName("taro");
        userEntity.setEmail("test@test.com");
        userEntity.setPassword("pass");

        // Act
        // テストデータを設定
        var resultUserEntity = testEntityManager.persistAndFlush(userEntity);

        // Assert
        assertTrue(resultUserEntity.getId() > 0);
        assertEquals(userEntity.getUserId(), resultUserEntity.getUserId());
        assertEquals(userEntity.getFirstName(), resultUserEntity.getFirstName());
        assertEquals(userEntity.getLastName(), resultUserEntity.getLastName());
        assertEquals(userEntity.getEmail(), resultUserEntity.getEmail());
        assertEquals(userEntity.getPassword(), resultUserEntity.getPassword());
    }
}

TestEntityManagerはEntityManagerの代わり
情報を永続化し、すぐにデータベーステーブルと同期させることができる

JPQLクエリ、ソースコードのテスト

UserRepository.java

// テスト対象のリポジトリ
@Repository
public interface UserRepository extends PagingAndSortingRepository<UserEntity, Long> {

    @Query("select user from UserEntity user where user.email like %:emailDomain")
    List<UserEntity> findUsersWithEmailEndingWith(@Param("emailDomain") String emailDomain);
}
    
UserRepositoryTest.java

//コード省略

    @Autowired
    private TestEntityManager testEntityManager;
    private final String email = "test@test.com";
    private final String email2 = "test@test.com";

    @BeforeEach
    void setup() {
        // ユーザー作成
        // first user
        var userEntity = new UserEntity();
        userEntity.setUserId(userId1);
        userEntity.setEmail(email1);
        userEntity.setPassword("12345678");
        userEntity.setFirstName("tanaka");
        userEntity.setLastName("ichiro");
        // テストデータを設定
        testEntityManager.persistAndFlush(userEntity);

        // second user
        var userEntity2 = new UserEntity();
        userEntity2.setUserId(userId2);
        userEntity2.setEmail(email2);
        userEntity2.setPassword("abcdefgh");
        userEntity2.setFirstName("takeda");
        userEntity2.setLastName("hanako");
        // テストデータを設定
        testEntityManager.persistAndFlush(userEntity2);
    }

    @Test
    void testfindUsersWithEmailEndingWith() {
         // Arrange
        var userEntity = new UserEntity();
        userEntity.setUserId(UUID.randomUUID().toString());
        userEntity.setEmail("test@gmail.com");
        userEntity.setPassword("pass");
        userEntity.setFirstName("yamada");
        userEntity.setLastName("taro");
        testEntityManager.persistAndFlush(userEntity);
        
        var emailDomailName = "@gmail.com";

        // Act
        var users = useraRepository.findUsersWithEmailEndingWith(emailDomainName);

        // Assert
        // emailドメインが"@gmail.com"のユーザーは一件しか取得できないこと
        Assertions.assertEquals(1,users.size());
        //emailドメインが一致すること
        Assertions.assertTrue(users.get(0).getEmail().endsWith(emailDomainName));
    }

15
15
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
15
15