JUnit5の使い方

JUnit5の使い方をご紹介します。

詳細を確認したい場合には JUnit5の公式github を確認すると良いです。

JUnitとは

JUnitとはJavaプログラムのUnitテスト(単体テスト)を行うフレームワークです。2018年にメジャーバージョンアップされてJUnit5になりました。

一度テストを作成してしまえば、すぐに何度でもテストを実行してプログラムのチェックが出来るため、開発・保守の高速化が期待出来ます。Javaでシステムを構築するならJUnit5を使わない手はありません。

さらにシステム開発を高速化するために、開発と並行してJUnit5によるテストコードを作成するのがオススメです。

また、テスト粒度を細かくしすぎるとテストコードのメンテナンスに時間がかかるようになるので、テストを作成するのは機能の入出力重点的に確認が必要な個所のみにするのがオススメです。

フレームワークのインストール

Spring boot 2.2以上からは、JUnit5がデフォルトなのでイニシャライザーでJUnitを指定すれば良いのですが、それ以前のSpring BootだったりするとMavenの設定をしてJUnitをインストールする必要があります。

JUnit5は大きく3つのフレームワーク(junit-jupiter、junit-platform、junit.vintage)から構成されていますそれぞれについて説明します。

1.junit-jupiter

JUnit5のフレームワークです。

2.junit-platform

テストのランチャーです。凝った起動をするのでなければ必要ないかもしれません。

3.junit.vintage

JUnit4、JUnit3の記述で書かれたテストコードをJUnit5環境で実行する際に使用します。JUnit5で記述した場合には必要ありません。

Mavenでフレームワークを取得する場合に、実際には次のような記述が最もシンプルな記述です。公式サイトのサンプルより抜粋します。

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
  <junit.jupiter.version>5.6.1</junit.jupiter.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>${junit.jupiter.version}</version>
    <scope>test</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.8.1</version>
    </plugin>
    <plugin>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.22.2</version>
    </plugin>
  </plugins>
</build>

参考:junit5-samples/pom.xml at master · junit-team/junit5-samples

テストコードの記述

最もシンプルな記述で実際にテストを書いてみます。

import static org.junit.jupiter.api.Assertions.assertEquals;
import example.util.Calculator;
import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {

  private final Calculator calculator = new Calculator();

  @Test
  void addition() {
    assertEquals(2, calculator.add(1, 1));
  }

}

参考:2. Writing Tests – JUnit 5 User Guide

JUnit5のおすすめ機能

JUnit5で新しく追加された便利な機能をご紹介します。便利な機能を使うだけでテストコードを簡略化することが出来、時間短縮および品質向上、保守性の向上が期待できます。

ネストしたテスト

@Nestedをテストの前に付与するとテストを階層化することが出来ます。子クラスは親クラスのフィールドを使用することが出来ます。これにより、テストコードを短くすることが出来ます。

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.EmptyStackException;
import java.util.Stack;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

参考:2.11. Nested Tests – JUnit 5 User Guide

パラメーター化テスト

異なる引数でテストを複数回簡単に実施することが出来ます。@ParameterizedTestをテストの前に付与して、@ValueSourceで引数を複数指定します。

実際には次のように書きます。このように書くと指定された引数を使用して、3回テストが実行されます。

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
    assertTrue(isPalindrome(candidate));
}

参考:2.15. Parameterized Tests – JUnit 5 User Guide