1. 概要

Truth は、テストアサーションと失敗メッセージを読みやすくするために設計された流暢で柔軟なオープンソーステストフレームワークです。

この記事では、 Truth フレームワークの主な機能を調べ、その機能を紹介する例を実装します。

2. Mavenの依存関係

まず、 truetruth-java8-extensionpom.xml:に追加する必要があります。

<dependency>
    <groupId>com.google.truth</groupId>
    <artifactId>truth</artifactId>
    <version>0.32</version>
</dependency>
<dependency>
    <groupId>com.google.truth.extensions</groupId>
    <artifactId>truth-java8-extension</artifactId>
    <version>0.32</version>
    <scope>test</scope>
</dependency>

最新バージョンのtruthおよびtruth-java8-extensionはMavenCentralで見つけることができます。

3. 序章

Truth を使用すると、さまざまなクラスの読み取り可能なアサーションと失敗メッセージを書き込むことができます。

  • 標準Java–プリミティブ、配列、文字列、オブジェクト、コレクション、スローアブル、クラスなど。
  • Java 8 オプションのおよびStreamインスタンス
  • Guava オプションマルチマップマルチセット、およびテーブルオブジェクト
  • カスタムタイプ–後で説明するように、サブジェクトクラスを拡張します

TruthクラスとTruth8クラスを通じて、ライブラリは、テスト対象の値またはオブジェクトであるsubjectで機能するアサーションを作成するためのユーティリティメソッドを提供します。

主題がわかれば、 Truthは、コンパイル時に、その主題についてどの命題が知られているかを推論できます。 これにより、その特定の主題に固有の命題メソッドを宣言する値のラッパーを返すことができます。

たとえば、リストでアサートする場合、 Truth は、 contains() containsAnyOf()などのメソッドを定義するIterableSubjectインスタンスを返します。その他。 Map でアサートすると、 containsEntry() containsKey()などのメソッドを宣言するMapSubjectが返されます。

4. 入門

アサーションの記述を開始するには、最初にTruthのエントリポイントをインポートしましょう。

import static com.google.common.truth.Truth.*;
import static com.google.common.truth.Truth8.*;

次に、以下のいくつかの例で使用する簡単なクラスを作成しましょう。

public class User {
    private String name = "John Doe";
    private List<String> emails
      = Arrays.asList("[email protected]", "[email protected]");

    public boolean equals(Object obj) {
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        User other = (User) obj;
        return Objects.equals(this.name, other.name);
    }

    // standard constructors, getters and setters
}

カスタムのequals()メソッドに注目してください。このメソッドでは、2つのUserオブジェクトの名前が等しい場合は等しいと述べています。

5. 標準のJavaアサーション

このセクションでは、標準のJavaタイプのテストアサーションを作成する方法の詳細な例を示します。

5.1. オブジェクトアサーション

Truth は、オブジェクトに対してアサーションを実行するためのSubjectラッパーを提供します。 Subject は、ライブラリ内の他のすべてのラッパーの親でもあり、 Object (この場合は User )が別のオブジェクトと等しいかどうかを判断するためのメソッドを宣言します:

@Test
public void whenComparingUsers_thenEqual() {
    User aUser = new User("John Doe");
    User anotherUser = new User("John Doe");

    assertThat(aUser).isEqualTo(anotherUser);
}

または、リスト内の特定のオブジェクトと等しい場合:

@Test
public void whenComparingUser_thenInList() {
    User aUser = new User();

    assertThat(aUser).isIn(Arrays.asList(1, 3, aUser, null));
}

またはそうでない場合:

@Test
public void whenComparingUser_thenNotInList() {
    // ...

    assertThat(aUser).isNotIn(Arrays.asList(1, 3, "Three"));
}

nullかどうか:

@Test
public void whenComparingUser_thenIsNull() {
    User aUser = null;

    assertThat(aUser).isNull();
}

@Test
public void whenComparingUser_thenNotNull() {
    User aUser = new User();

    assertThat(aUser).isNotNull();
}

または、特定のクラスのインスタンスの場合:

@Test
public void whenComparingUser_thenInstanceOf() {
    // ...

    assertThat(aUser).isInstanceOf(User.class);
}

Subjectクラスには他のアサーションメソッドがあります。 それらすべてを見つけるには、件名のドキュメントを参照してください。

次のセクションでは、特定のタイプ Truthがサポートするそれぞれに最も関連する方法に焦点を当てます。 ただし、Subjectクラスのすべてのメソッドも適用できることに注意してください。

5.2. Integer Float、、およびDoubleアサーション

Integer Float、、および Double インスタンスは、等しいかどうかを比較できます。

@Test
public void whenComparingInteger_thenEqual() {
    int anInt = 10;

    assertThat(anInt).isEqualTo(10);
}

それらが大きい場合:

@Test
public void whenComparingFloat_thenIsBigger() {
    float aFloat = 10.0f;

    assertThat(aFloat).isGreaterThan(1.0f);
}

以下:

@Test
public void whenComparingDouble_thenIsSmaller() {
    double aDouble = 10.0f;

    assertThat(aDouble).isLessThan(20.0);
}

さらに、、Float 、および Double インスタンスをチェックして、期待される精度内にあるかどうかを確認することもできます。

@Test
public void whenComparingDouble_thenWithinPrecision() {
    double aDouble = 22.18;

    assertThat(aDouble).isWithin(2).of(23d);
}

@Test
public void whenComparingFloat_thenNotWithinPrecision() {
    float aFloat = 23.04f;

    assertThat(aFloat).isNotWithin(1.3f).of(100f);
}

5.3. BigDecimalアサーション

一般的なアサーションに加えて、このタイプはそのスケールを無視して比較できます。

@Test
public void whenComparingBigDecimal_thenEqualIgnoringScale() {
    BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3);

    assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0));
}

5.4. ブール値アサーション

isTrue() isFalse()の2つの関連するメソッドのみが提供されます。

@Test
public void whenCheckingBoolean_thenTrue() {
    boolean aBoolean = true;

    assertThat(aBoolean).isTrue();
}

5.5. 文字列アサーション

Stringが特定のテキストで始まるかどうかをテストできます。

@Test
public void whenCheckingString_thenStartsWith() {
    String aString = "This is a string";

    assertThat(aString).startsWith("This");
}

さらに、文字列に特定の文字列が含まれているかどうか、期待値で終わっているかどうか、または空であるかどうかを確認できます。 これらおよびその他のメソッドのテストケースは、ソースコードで入手できます。

5.6. アレイアサーション

Array をチェックして、他のアレイと等しいかどうかを確認できます。

@Test
public void whenComparingArrays_thenEqual() {
    String[] firstArrayOfStrings = { "one", "two", "three" };
    String[] secondArrayOfStrings = { "one", "two", "three" };

    assertThat(firstArrayOfStrings).isEqualTo(secondArrayOfStrings);
}

またはそれらが空の場合:

@Test
public void whenCheckingArray_thenEmpty() {
    Object[] anArray = {};

    assertThat(anArray).isEmpty();
}

5.7. 比較可能アサーション

Comparable が別のインスタンスよりも大きいか小さいかをテストするだけでなく、それらが少なくとも指定された値であるかどうかを確認できます。

@Test
public void whenCheckingComparable_thenAtLeast() {
    Comparable<Integer> aComparable = 5;

    assertThat(aComparable).isAtLeast(1);
}

また、それらが特定の範囲内にあるかどうかをテストできます。

@Test
public void whenCheckingComparable_thenInRange() {
    // ...

    assertThat(aComparable).isIn(Range.closed(1, 10));
}

または特定のリスト:

@Test
public void whenCheckingComparable_thenInList() {
    // ...

    assertThat(aComparable).isIn(Arrays.asList(4, 5, 6));
}

クラスのcompareTo()メソッドに従って、2つのCompareableインスタンスが同等であるかどうかをテストすることもできます。

まず、 User クラスを変更して、Compareableインターフェイスを実装しましょう。

public class User implements Comparable<User> {
    // ...
    
    public int compareTo(User o) {
        return this.getName().compareToIgnoreCase(o.getName());
    }
}

ここで、同じ名前の2人のユーザーが同等であると主張しましょう。

@Test
public void whenComparingUsers_thenEquivalent() {
    User aUser = new User();
    aUser.setName("John Doe");

    User anotherUser = new User();
    anotherUser.setName("john doe");

    assertThat(aUser).isEquivalentAccordingToCompareTo(anotherUser);
}

5.8. イテレータアサーション

Iterable インスタンスのサイズをアサートすることに加えて、それが空であるか重複がないかにかかわらず、 Iterable での最も一般的なアサーションは、いくつかの要素が含まれていることです。

@Test
public void whenCheckingIterable_thenContains() {
    List<Integer> aList = Arrays.asList(4, 5, 6);

    assertThat(aList).contains(5);
}

別のIterableの要素が含まれていること:

@Test
public void whenCheckingIterable_thenContainsAnyInList() {
    List<Integer> aList = Arrays.asList(1, 2, 3);

    assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10));
}

そして、サブジェクトが別のように同じ要素を同じ順序で持っていること:

@Test
public void whenCheckingIterable_thenContainsExactElements() {
    List<String> aList = Arrays.asList("10", "20", "30");
    List<String> anotherList = Arrays.asList("10", "20", "30");

    assertThat(aList)
      .containsExactlyElementsIn(anotherList)
      .inOrder();
}

カスタムコンパレータを使用して注文した場合:

@Test
public void givenComparator_whenCheckingIterable_thenOrdered() {
    Comparator<String> aComparator
      = (a, b) -> new Float(a).compareTo(new Float(b));

    List<String> aList = Arrays.asList("1", "012", "0020", "100");

    assertThat(aList).isOrdered(aComparator);
}

5.9. マップアサーション

Map インスタンスが空であるかどうか、または特定のサイズであると主張することに加えて、 特定のエントリがあるかどうかを確認できます。

@Test
public void whenCheckingMap_thenContainsEntry() {
    Map<String, Object> aMap = new HashMap<>();
    aMap.put("one", 1L);

    assertThat(aMap).containsEntry("one", 1L);
}

特定のキーがある場合:

@Test
public void whenCheckingMap_thenContainsKey() {
    // ...

    assertThat(map).containsKey("one");
}

または、別の Map と同じエントリがある場合:

@Test
public void whenCheckingMap_thenContainsEntries() {
    Map<String, Object> aMap = new HashMap<>();
    aMap.put("first", 1L);
    aMap.put("second", 2.0);
    aMap.put("third", 3f);

    Map<String, Object> anotherMap = new HashMap<>(aMap);

    assertThat(aMap).containsExactlyEntriesIn(anotherMap);
}

5.10. 例外アサーション

Exception オブジェクトには、重要な2つのメソッドのみが提供されます。

例外の原因に対処するアサーションを記述できます。

@Test
public void whenCheckingException_thenInstanceOf() {
    Exception anException
      = new IllegalArgumentException(new NumberFormatException());

    assertThat(anException)
      .hasCauseThat()
      .isInstanceOf(NumberFormatException.class);
}

またはそのメッセージに:

@Test
public void whenCheckingException_thenCauseMessageIsKnown() {
    Exception anException
      = new IllegalArgumentException("Bad value");

    assertThat(anException)
      .hasMessageThat()
      .startsWith("Bad");
}

5.11. クラスアサーション

Class アサーションには、クラスが別のクラスに割り当て可能かどうかをテストできる重要なメソッドが1つだけあります。

@Test
public void whenCheckingClass_thenIsAssignable() {
    Class<Double> aClass = Double.class;

    assertThat(aClass).isAssignableTo(Number.class);
}

6. Java8アサーション

オプションStreamは、Truthがサポートする唯一の2つのJava8タイプです。

6.1. オプションのアサーション

オプションを確認するための3つの重要な方法があります。

特定の値があるかどうかをテストできます。

@Test
public void whenCheckingJavaOptional_thenHasValue() {
    Optional<Integer> anOptional = Optional.of(1);

    assertThat(anOptional).hasValue(1);
}

値が存在する場合:

@Test
public void whenCheckingJavaOptional_thenPresent() {
    Optional<String> anOptional = Optional.of("Baeldung");

    assertThat(anOptional).isPresent();
}

または値が存在しない場合:

@Test
public void whenCheckingJavaOptional_thenEmpty() {
    Optional anOptional = Optional.empty();

    assertThat(anOptional).isEmpty();
}

6.2. ストリームアサーション

Stream のアサーションは、Iterableのアサーションと非常によく似ています。

たとえば、特定のStreamIterableのすべてのオブジェクトが同じ順序で含まれているかどうかをテストできます。

@Test
public void whenCheckingStream_thenContainsInOrder() {
    Stream<Integer> anStream = Stream.of(1, 2, 3);

    assertThat(anStream)
      .containsAllOf(1, 2, 3)
      .inOrder();
}

その他の例については、Iterableアサーションのセクションを参照してください。

7. グアバアサーション

このセクションでは、TruthでサポートされているGuavaタイプのアサーションの例を示します。

7.1. オプションアサーション

Guavaオプションには3つの重要なアサーションメソッドもあります。 hasValue()および isPresent()メソッドは、Java8オプションとまったく同じように動作します。

ただし、 isEmpty()の代わりに、オプションが存在しないことを表明する代わりに、 isAbsent()を使用します。

@Test
public void whenCheckingGuavaOptional_thenIsAbsent() {
    Optional anOptional = Optional.absent();

    assertThat(anOptional).isAbsent();
}

7.2. マルチマップアサーション

Multimapと標準のMapアサーションは非常に似ています。

注目すべき違いの1つは、 Multimap 内のキーの複数の値を取得し、それらの値に対してアサーションを作成できることです。

「one」キーの値のサイズが2であるかどうかをテストする例を次に示します。

@Test
public void whenCheckingGuavaMultimap_thenExpectedSize() {
    Multimap<String, Object> aMultimap = ArrayListMultimap.create();
    aMultimap.put("one", 1L);
    aMultimap.put("one", 2.0);

    assertThat(aMultimap)
      .valuesForKey("one")
      .hasSize(2);
}

その他の例については、マップアサーションのセクションを参照してください。

7.3. マルチセットアサーション

Multiset オブジェクトのアサーションには、 Iterable のアサーションと、キーに特定の出現回数があるかどうかを確認するための1つの追加メソッドが含まれます。

@Test
public void whenCheckingGuavaMultiset_thenExpectedCount() {
    TreeMultiset<String> aMultiset = TreeMultiset.create();
    aMultiset.add("baeldung", 10);

    assertThat(aMultiset).hasCount("baeldung", 10);
}

7.4. テーブルアサーション

サイズや空の場所をチェックするだけでなく、 Table をチェックして、特定の行と列の特定のマッピングが含まれているかどうかを確認できます。

@Test
public void whenCheckingGuavaTable_thenContains() {
    Table<String, String, String> aTable = TreeBasedTable.create();
    aTable.put("firstRow", "firstColumn", "baeldung");

    assertThat(aTable).contains("firstRow", "firstColumn");
}

または、特定のセルが含まれている場合:

@Test
public void whenCheckingGuavaTable_thenContainsCell() {
    Table<String, String, String> aTable = getDummyGuavaTable();

    assertThat(aTable).containsCell("firstRow", "firstColumn", "baeldung");
}

さらに、特定の行、列、または値が含まれているかどうかを確認できます。 関連するテストケースについては、ソースコードを参照してください。

8. カスタム障害メッセージとラベル

アサーションが失敗すると、 Truth は、何が悪かったのかを正確に示す非常に読みやすいメッセージを表示します。 ただし、何が起こったかについての詳細を提供するために、これらのメッセージにさらに情報を追加する必要がある場合があります。

Truth を使用すると、これらの失敗メッセージをカスタマイズできます。

@Test
public void whenFailingAssertion_thenCustomMessage() {
    assertWithMessage("TEST-985: Secret user subject was NOT null!")
      .that(new User())
      .isNull();
}

テストを実行すると、次の出力が得られます。

TEST-985: Secret user subject was NOT null!:
  Not true that <com.baeldung.testing.truth.User@ae805d5e> is null

また、エラーメッセージで件名の前に表示されるカスタムラベルを追加することもできます。 これは、オブジェクトに有用な文字列表現がない場合に便利です。

@Test
public void whenFailingAssertion_thenMessagePrefix() {
    User aUser = new User();

    assertThat(aUser)
      .named("User [%s]", aUser.getName())
      .isNull();
}

テストを実行すると、次の出力が表示されます。

Not true that User [John Doe]
  (<com.baeldung.testing.truth.User@ae805d5e>) is null

9. 拡張機能

Truth を拡張すると、カスタムタイプのサポートを追加できるようになります。 これを行うには、次のようなクラスを作成する必要があります。

  • Subjectクラスまたはそのサブクラスの1つを拡張します
  • FailureStrategyとカスタムタイプのインスタンスの2つの引数を受け入れるコンストラクターを定義します
  • SubjectFactory タイプのフィールドを宣言します。これは、Truthがカスタムサブジェクトのインスタンスを作成するために使用します
  • カスタムタイプを受け入れる静的assertThat()メソッドを実装します
  • テストアサーションAPIを公開します

Truth を拡張する方法がわかったので、Userタイプのオブジェクトのサポートを追加するクラスを作成しましょう。

public class UserSubject
  extends ComparableSubject<UserSubject, User> {

    private UserSubject(
      FailureStrategy failureStrategy, User target) {
        super(failureStrategy, target);
    }

    private static final
      SubjectFactory<UserSubject, User> USER_SUBJECT_FACTORY
      = new SubjectFactory<UserSubject, User>() {

        public UserSubject getSubject(
          FailureStrategy failureStrategy, User target) {
            return new UserSubject(failureStrategy, target);
        }
    };

    public static UserSubject assertThat(User user) {
        return Truth.assertAbout(USER_SUBJECT_FACTORY).that(user);
    }

    public void hasName(String name) {
        if (!actual().getName().equals(name)) {
            fail("has name", name);
        }
    }

    public void hasNameIgnoringCase(String name) {
        if (!actual().getName().equalsIgnoreCase(name)) {
            fail("has name ignoring case", name);
        }
    }

    public IterableSubject emails() {
        return Truth.assertThat(actual().getEmails());
    }
}

これで、カスタムサブジェクトの assertThat()メソッドを静的にインポートして、いくつかのテストを作成できます。

@Test
public void whenCheckingUser_thenHasName() {
    User aUser = new User();

    assertThat(aUser).hasName("John Doe");
}

@Test
public void whenCheckingUser_thenHasNameIgnoringCase() {
    // ...

    assertThat(aUser).hasNameIgnoringCase("john doe");
}

@Test
public void givenUser_whenCheckingEmails_thenExpectedSize() {
    // ...

    assertThat(aUser)
      .emails()
      .hasSize(2);
}

10. 結論

このチュートリアルでは、 Truth によって、より読みやすいテストと失敗メッセージを作成できる可能性を探りました。

サポートされているJavaおよびGuavaタイプ、カスタマイズされた失敗メッセージ、およびカスタムサブジェクトを使用した拡張Truthで最も一般的なアサーションメソッドを紹介しました。

いつものように、この記事の完全なソースコードは、Githubにあります。