Googleの真実を使ったテスト

1概要

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

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

2 Mavenの依存関係

まず、 pom.xmlに truth truth-java8-extension__を追加する必要があります。

<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- Maven Centralの[java8-extension

3前書き

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

  • 標準Java - プリミティブ、配列、文​​字列、オブジェクト、コレクション、

スロー、クラスなど

  • Java 8 - Optional および Stream インスタンス

  • Guava - Optional Multimap Multiset 、および Table オブジェクト

  • カスタム型 - 後で見るように、 Subject クラスを拡張することによって

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 クラスには他のアサーションメソッドがあります。それらすべてを発見するには、http://google.github.io/truth/api/latest/com/google/common/truth/Subject.html[ Subject documentation]を参照してください。

次のセクションでは、** 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. Boolean アサーション

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

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

    assertThat(aBoolean).isTrue();
}

5.5. String アサーション

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

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

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

さらに、文字列に特定の文字列が含まれているかどうか、期待値で終了しているかどうか、または空であるかどうかを確認できます。これらおよび他の方法のためのテストケースはソースコードで利用可能です。

5.6. 配列アサーション

__Array __sを調べて、それらが他の配列と等しいかどうかを確認できます。

@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つの Comparable__インスタンスが等価かどうかをテストすることもできます。

まず、 Comparable インターフェースを実装するように User クラスを変更しましょう。

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 アサーション

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 アサーション

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. Java 8アサーション

Optional Stream は、 Truth がサポートする唯一の2つのJava 8タイプです。

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

Optional を検証するための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 アサーション

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

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

@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つの重要なアサーションメソッド Optional もあります。

hasValue() および isPresent() メソッドは、Java 8の Optional とまったく同じように動作します。

しかし、 Optional が存在しないことを主張するために isEmpty() の代わりに isAbsent() を使います。

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

    assertThat(anOptional).isAbsent();
}

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

マルチマップアサーションと標準のマップアサーションはよく似ています。

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);
}

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

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 アサーション

サイズや空の場所を確認するだけでなく、 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 <[email protected]> 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]  (<[email protected]>) is null

9拡張機能

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

  • Subject クラスまたはそのサブクラスの1つを拡張します。

  • 2つの引数を受け取るコンストラクタを定義します - FailureStrategy

そして私たちのカスタムタイプのインスタンス ** Truth が使用する SubjectFactory タイプのフィールドを宣言します。

カスタム件名のインスタンスを作成する ** 私たちのカスタム型を受け入れる静的 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 用の最も一般的なアサーションメソッドを紹介しました。

いつものように、この記事の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/testing-modules/testing[Githubに追加]を見てください。