JUnit5で変わるテストの書き方

JUnit5が案外よさげなので、JUnit5を使うとどんな感じでテストが変わるのか考えてみます。
実際にどこが変わったかとか、使い方自体はいろいろまとめられたブログがあるし、公式ドキュメントも読みやすいのでそちらを。
http://junit.org/junit5/docs/current/user-guide/

メソッドごとのテスト

JUnit5でいいのは、Nestedですね。
いままで、いろんなメソッドを対象にしたテストが入り混じってたと思います。

import org.junit.Before;
import org.junit.Test;
public class PurchaseTest {
    @Before
    public void setup() {
        // 全体のセットアップ
        // purchase()用のセットアップ
        // history()用のセットアップ
    }
    
    @Test
    public void purchase_success() {
    }
    
    @Test
    public void purchase_insufficient() {
    }
    
    @Test
    public void purchase_soldout(){ 
    }
    
    @Test
    public void history() {
    }
    
    @Test
    public void history_nodata() {
    }
}

こんな感じ。hoge_case1みたいなのがたくさん並んで、わかりにくかった。
各メソッド用のセットアップも、全部まとめるか、セットアップ用メソッドを定義して呼び出すか、みたいになっていました。


これをNested使うと、こう。

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
public class PurchaseTest {
    @BeforeEach
    void setup() {
        // 全体のセットアップ
    }
    
    @Nested
    class purchase {
        @BeforeEach
        void setup() {
            // purchase()用のセットアップ
        }
        
        @Test
        void success() {
        }

        @Test
        @DisplayName("金額不足")
        void insufficient() {
        }

        @Test
        void soldout(){ 
        }
    }
    
    @Nested
    class history {
        @BeforeEach
        void setup() {
            // history()用のセットアップ
        }
        
        @Test
        void normal() {
        }

        @Test
        void nodata() {
        }
    }
}

public指定が不要になったのも、地味にありがたい。
メソッドごとのテストをNestedするためのクラス名は、メソッドにあわせて小文字始まりにするのもいいかなと思うけど、IDEとかに警告されそうでもある。

テスト区分

JUnit5からcategoryの代わりにtagが導入されましたが、アノテーションが短くなったところで、いちいちつけるのは面倒。
メタアノテーションが使えるので、これも便利に。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("integration")
@Test
public @interface MySqlIT {
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("integration")
@Test
public @interface SpringIT {
}

こんな感じのアノテーションを定義して、

@MySqlIT
void store() {
  // 外部のMySQLにアクセスしちゃうテスト
}

@SpringIT
void login() {
  // よそのSpringを呼び出したりするテスト
}

みたいな感じでテストを書ける。


で、ビルドスクリプトには一括でexcludeできたり。

<configuration>
  <properties>
    <excludeTags>integration</excludeTags>
  </properties>
</configuration>


細分化しすぎると使いにくそうだけど。

パラメタライズテスト

ちょっとずつパラメータ変えたテストを書くのも面倒だったけど、だいぶ楽になります。
junit-jupiter-paramsを別にdependencyに付け加える必要があるので注意。

@ParameterizedTest(name="param test {0}")
@ValueSource(strings = {"one", "two"})
void paramTest(String param) {
    Assertions.assertEquals("one", param);
}

値の生成には、@ValueSourceのほかにも@CsvSourceとか@MethodSourceとかあるので便利そう。

pom

参考までに、これ動かしたpom。
モジュール構成はこんな感じになってます。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>kis</groupId>
    <artifactId>junit5sample</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <junit.jupiter.version>5.0.1</junit.jupiter.version>
        <junit.platform.version>1.0.1</junit.platform.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19.1</version>
                <dependencies>
                    <dependency>
                        <groupId>org.junit.platform</groupId>
                        <artifactId>junit-platform-surefire-provider</artifactId>
                        <version>${junit.platform.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <version>${junit.platform.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>        
    </dependencies>
</project>