CLOVER🍀

That was when it all began.

AssertJ 2.0の新機能を試す

このブログでJavaのテストコードを書いている時に使っている、AssertJの2.0が出ましたということで、その新機能をいくつか試してみました。

AssertJ core 2.0.0 release : New and noteworthy
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0

この中から、いくつかかいつまんでご紹介。

AssertJを使うために必要な依存関係は、こちらになります。

    <dependency>
      <groupId>org.assertj</groupId>
      <artifactId>assertj-core</artifactId>
      <version>2.0.0</version>
      <scope>test</scope>
    </dependency>

今回は、JUnitと組み合わせて使いました。

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

import文

ここからしばらく紹介するコードは、以下のimport文があることを前提にします。

import static org.assertj.core.api.Assertions.*;

普通、AssertJを使う時にはこのクラスをstatic importします。

java.nio.file.Pathに対するAssertion

NIO.2のPathに対してAssertionを行うことができるようになりました。

Path assertions
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0-path-assertions

サンプル。

    @Test
    public void testPath() {
        Path pomXml = Paths.get("pom.xml");
        assertThat(pomXml).exists();
        assertThat(pomXml).isRelative();
        assertThat(pomXml).isRegularFile();

        Path srcDir = Paths.get("src/test/java");
        assertThat(srcDir).exists();
        assertThat(srcDir).isRelative();
        assertThat(srcDir).isDirectory();
        assertThat(srcDir)
            .startsWith(Paths.get("src/"))
            .startsWith(Paths.get("src/test"));
        assertThat(srcDir)
            .endsWith(Paths.get("java"))
            .endsWith(Paths.get("test/java"));

        Path fooBarPath = Paths.get("/foo/bar");
        assertThat(fooBarPath).doesNotExist();
        assertThat(fooBarPath).isAbsolute();
    }

例外に対するテストケース

assertThatThrownByメソッドを使用することで、より簡単に(?)例外に対するテストが書けるようになったらしいのですが…。

Ease testing code raising exceptions/throwables
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0-thrown-by

サンプル。

    @Test
    public void testThrownException() {
        class Calc {
            public int div(int a, int b) {
                return a / b;
            }
        }

        Calc c = new Calc();
        assertThatThrownBy(() -> c.div(10, 0))
            .isInstanceOf(ArithmeticException.class)
            .hasMessage("/ by zero");
    }

ここでassertThatThrownByメソッドに渡しているLambda式は、org.assertj.core.api.ThrowableAssert.ThrowingCallableインターフェースの実装になります。callメソッドを実装するだけでいいので、Lambda式で書けるわけですが…。

メソッド呼び出しの形にしないと、コンパイルが通りません…。

ちょっとハマりました。もうちょっとなんとかならないかな?書き方が悪かっただけならいいんですが…。

Map New Assertions

java.util.Mapに対するAssertionとして、以下の2つが追加されました。

New Map assertion : containsValues
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0-containsValues

New Map assertion : doesNotContainKeys
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0-doesNotContainKeys

Mapに対して、指定された値(可変長引数)が含まれていることを確認する、containsValues。

    @Test
    public void testContainsValues() {
        Map<String, String> map = new HashMap<>();
        map.put("foo", "bar");
        map.put("fuga", "hoge");

        assertThat(map).containsValues("bar", "hoge");
    }

指定した値のいずれかがMapに含まれていなかった場合は、テストが失敗します。

Mapに対して、指定されたキー(可変長引数)が含まれていないことを確認する、.doesNotContainKeys。

    @Test
    public void testDoesNotContainKeys() {
        Map<String, String> map = new HashMap<>();
        map.put("foo", "bar");
        map.put("fuga", "hoge");

        assertThat(map).doesNotContainKeys("key1", "key2");
    }

こちらは、指定したキーのいずれかがMapに含まれていなかった場合は、テストが失敗します。

asList、asString

ListおよびStringに、メソッドチェインでダウンキャストするためのメソッドみたいです。

Allow to switch to List assertions using asList()
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0-asList

Allow to switch to String assertions using asString()
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0-asString

サンプル。

    @Test
    public void testAsList() {
        Collection<String> list = Arrays.asList("elem1", "elem2");

        assertThat(list).asList().isSorted();
    }

結局、これとそう変わらないんですけどね…。

        assertThat((List<String>)list).isSorted();

asStringの方も。

    @Test
    public void testAsString() {
        Object message = "Hello World";

        assertThat(message).asString().contains("World");
    }

使う…?

BDD soft assertions

ここからは、ちょっと毛色を変えてBDD soft assertionsという機能に絞って書きます。

BDD soft assertions
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0-bdd-soft-assertions

BDD soft assertionsでは、使えるクラスが3つほどあるので、それぞれご紹介します。

BDDSoftAssertions

まずはBDDSoftAssertions。import文は、こちら。

import org.assertj.core.api.BDDSoftAssertions;

サンプルはこんな感じです。

    @Test
    public void testBddSoft() {
        BDDSoftAssertions softly = new BDDSoftAssertions();

        softly.then("Hello World")
            .as("Hello World")
            .contains("World");
        softly.then("Hello")
            .as("Hello is not equal to World")
            .isEqualTo("World");
        softly.then("Java")
            .as("Java is not equal to Not Java")
            .isEqualTo("Not Java");
        softly.then(10 + 5)
            .as("Plus")
            .isEqualTo(3);

        softly.assertAll();
    }

最後に、BDDSoftAssertions#assertAllの呼び出しを行わないと、それまで書いたテストコードが評価されません。

普通にassertThatとかで書く時と何が違うかというと、テストメソッド内のあるでAssertionに失敗しても、そのままテストが続くということですね。

このサンプルでは、いくつかテストに失敗するコードになっていますが、結果はこのようになります。

testBddSoft(org.littlewings.assertj.newfeature.New20FeatureBddTest)  Time elapsed: 0.208 sec  <<< FAILURE!
org.assertj.core.api.SoftAssertionError: 
The following 3 assertions failed:
1) [Hello is not equal to World] expected:<"[World]"> but was:<"[Hello]">
2) [Java is not equal to Not Java] expected:<"[Not ]Java"> but was:<"[]Java">
3) [Plus] expected:<[3]> but was:<[15]>

	at org.assertj.core.api.BDDSoftAssertions.assertAll(BDDSoftAssertions.java:140)
	at org.littlewings.assertj.newfeature.New20FeatureBddTest.testBddSoft(New20FeatureBddTest.java:26)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)

失敗したAssertionひとつひとつに対して、結果が出ます。

また、JUnitレポートのサマリとしては、まとめられてこんな表示になります。

Failed tests:   testBdd(org.littlewings.assertj.newfeature.New20FeatureBddTest): [Hello is not equal to World] expected:<"[World]"> but was:<"[Hello]">
  testBddSoft(org.littlewings.assertj.newfeature.New20FeatureBddTest): (..)

thenの後に付けているasはDescriptionですが、

        softly.then("Hello")
            .as("Hello is not equal to World")
            .isEqualTo("World");

なくても別にかまいません。

あと、前からBDDAssertionsというクラスもあったようです。

    @Test
    public void testBdd() {
        BDDAssertions.then("Hello World")
            .as("Hello World")
            .contains("World");
        BDDAssertions.then("Hello")
            .as("Hello is not equal to World")
            .isEqualTo("World");
        BDDAssertions.then("Java")
            .as("Java is not equal to Not Java")
            .isEqualTo("Not Java");
        BDDAssertions.then(10 + 5)
            .as("Plus")
            .isEqualTo(3);
    }

こちらは、Assertionに失敗すると即テスト終了というか、普通のAssertionの挙動になります。

ところで、BDDSoftAssertions#assertAllを呼び出さないとテストが評価されないわけですが、これをやめるためには、JUnitBDDSoftAssertionsかAutoCloseableBDDSoftAssertionsを使用します。

JUnitBDDSoftAssertions

JUnitの@Ruleで使用する形態で提供されている、BDDSoftAssertionsの拡張です。

import文はこちら。

import org.assertj.core.api.JUnitBDDSoftAssertions;

テストクラスのフィールドに、@Ruleアノテーションと共に定義します。

    @Rule
    public final JUnitBDDSoftAssertions softly = new JUnitBDDSoftAssertions();

あとは、JUnitBDDSoftAssertionsのメソッドを使って、テストを書いていきます。

    @Test
    public void testJUnitBdd1() {
        softly.then("Hello").isEqualTo("Hello");
        softly.then(10 + 5).isEqualTo(15);
    }

    @Test
    public void testJUnitBdd2() {
        softly.then("Hello World").isEqualTo("Hello World");
        softly.then("Hello").isEqualTo("World");
        softly.then("Java").isEqualTo("Not Java");
        softly.then(10 + 5).isEqualTo(3);
    }

    @Test
    public void testJUnitBdd3() {
        softly.then("Hello World").isEqualTo("World");
        softly.then("Hello").isEqualTo("World");
        softly.then("Java").isEqualTo("Groovy");
        softly.then(10 + 5).isEqualTo(3);
    }

assertAllはなくなりましたね。

このコードでは、後ろ2つのテストメソッドは失敗します。また、今回はasは端折りました。

結果は、こんな感じ。

testJUnitBdd2(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest)  Time elapsed: 0.012 sec  <<< FAILURE!
org.junit.ComparisonFailure: expected:<"[World]"> but was:<"[Hello]">
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest.testJUnitBdd2(New20FeatureJUnitBddTest.java:20)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
〜省略〜

testJUnitBdd2(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest)  Time elapsed: 0.013 sec  <<< FAILURE!
org.junit.ComparisonFailure: expected:<"[Not ]Java"> but was:<"[]Java">
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest.testJUnitBdd2(New20FeatureJUnitBddTest.java:21)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
〜省略〜

testJUnitBdd2(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest)  Time elapsed: 0.013 sec  <<< FAILURE!
org.junit.ComparisonFailure: expected:<[3]> but was:<[15]>
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest.testJUnitBdd2(New20FeatureJUnitBddTest.java:22)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
〜省略〜

testJUnitBdd3(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest)  Time elapsed: 0.003 sec  <<< FAILURE!
org.junit.ComparisonFailure: expected:<"[World]"> but was:<"[Hello]">
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest.testJUnitBdd3(New20FeatureJUnitBddTest.java:28)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
〜省略〜

最後のサマリでは、先ほどのBDDSoftAssertionsと異なり、個々の結果も並びます。

Failed tests:   testJUnitBdd2(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest): expected:<"[World]"> but was:<"[Hello]">
  testJUnitBdd2(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest): expected:<"[Not ]Java"> but was:<"[]Java">
  testJUnitBdd2(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest): expected:<[3]> but was:<[15]>
  testJUnitBdd3(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest): expected:<"[]World"> but was:<"[Hello ]World">
  testJUnitBdd3(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest): expected:<"[World]"> but was:<"[Hello]">
  testJUnitBdd3(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest): expected:<"[Groovy]"> but was:<"[Java]">
  testJUnitBdd3(org.littlewings.assertj.newfeature.New20FeatureJUnitBddTest): expected:<[3]> but was:<[15]>
AutoCloseableBDDSoftAssertions

AutoCloseableを実装した、BDDSoftAssertionsの拡張です。

Soft assertions automatic global verification with try-with-resources statement
http://joel-costigliola.github.io/assertj/assertj-core-news.html#assertj-core-2.0.0-auto-closeable-soft-assertions

import文はこちら。

import org.assertj.core.api.AutoCloseableBDDSoftAssertions;

try-with-resourcesで囲いつつ、テストコードを書きます。

    @Test
    public void tesetAutoCloseableBddSoft1() {
        try (AutoCloseableBDDSoftAssertions softly = new AutoCloseableBDDSoftAssertions()) {
            softly.then("Hello").isEqualTo("Hello");
            softly.then(10 + 5).isEqualTo(15);
        }
    }

    @Test
    public void tesetAutoCloseableBddSoft2() {
        try (AutoCloseableBDDSoftAssertions softly = new AutoCloseableBDDSoftAssertions()) {
            softly.then("Hello World").isEqualTo("Hello World");
            softly.then("Hello").isEqualTo("World");
            softly.then("Java").isEqualTo("Not Java");
            softly.then(10 + 5).isEqualTo(3);
        }
    }

    @Test
    public void tesetAutoCloseableBddSoft3() {
        try (AutoCloseableBDDSoftAssertions softly = new AutoCloseableBDDSoftAssertions()) {
            softly.then("Hello World").isEqualTo("World");
            softly.then("Hello").isEqualTo("World");
            softly.then("Java").isEqualTo("Groovy");
            softly.then(10 + 5).isEqualTo(3);
        }
    }

closeされる時に、assertAllが実行されるようです。

テスト結果は、BDDSoftAssertionsと同じです。

tesetAutoCloseableBddSoft2(org.littlewings.assertj.newfeature.New20FeatureAutoCloseableBddTest)  Time elapsed: 0.024 sec  <<< FAILURE!
org.assertj.core.api.SoftAssertionError: 
The following 3 assertions failed:
1) expected:<"[World]"> but was:<"[Hello]">
2) expected:<"[Not ]Java"> but was:<"[]Java">
3) expected:<[3]> but was:<[15]>

	at org.assertj.core.api.BDDSoftAssertions.assertAll(BDDSoftAssertions.java:140)
	at org.assertj.core.api.AutoCloseableBDDSoftAssertions.close(AutoCloseableBDDSoftAssertions.java:130)
	at org.littlewings.assertj.newfeature.New20FeatureAutoCloseableBddTest.tesetAutoCloseableBddSoft2(New20FeatureAutoCloseableBddTest.java:22)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
〜省略〜

tesetAutoCloseableBddSoft3(org.littlewings.assertj.newfeature.New20FeatureAutoCloseableBddTest)  Time elapsed: 0.001 sec  <<< FAILURE!
org.assertj.core.api.SoftAssertionError: 
The following 4 assertions failed:
1) expected:<"[]World"> but was:<"[Hello ]World">
2) expected:<"[World]"> but was:<"[Hello]">
3) expected:<"[Groovy]"> but was:<"[Java]">
4) expected:<[3]> but was:<[15]>

	at org.assertj.core.api.BDDSoftAssertions.assertAll(BDDSoftAssertions.java:140)
	at org.assertj.core.api.AutoCloseableBDDSoftAssertions.close(AutoCloseableBDDSoftAssertions.java:130)
	at org.littlewings.assertj.newfeature.New20FeatureAutoCloseableBddTest.tesetAutoCloseableBddSoft3(New20FeatureAutoCloseableBddTest.java:32)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
〜省略〜

サマリは、こちら。

Failed tests:   tesetAutoCloseableBddSoft2(org.littlewings.assertj.newfeature.New20FeatureAutoCloseableBddTest): (..)
  tesetAutoCloseableBddSoft3(org.littlewings.assertj.newfeature.New20FeatureAutoCloseableBddTest): (..)

まとめ

全部の新機能を紹介したわけではないのですが、まあ使うかも?と思われるものを試してみました。それほど新機能が多いわけでもないので、他を確認したい場合はRelease Notesを参照のこと。

BDDSoftAssertionsはどうなんでしょうね、Assertionsがひとつ失敗しても停止しないのは、良いような、そうでもないような。好みの問題なのでしょうか?

まあ、BDDSoftAssertionsのスタイルの中で、使うとすればJUnitBDDSoftAssertionsな気がします。