Googleの真実を使ったテスト

Google Truthを使用したテスト

1. 概要

Truthfluent and flexible open-source testing framework designed to make test assertions and failure messages more readable.です

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

2. Mavenの依存関係

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


    com.google.truth
    truth
    0.32


    com.google.truth.extensions
    truth-java8-extension
    0.32
    test

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

3. 前書き

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

  • Standard Java –プリミティブ、配列、文​​字列、オブジェクト、コレクション、スローアブル、クラスなど。

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

  • GuavaOptionalMultimapMultiset、およびTableオブジェクト

  • Custom types –後で説明するように、Subjectクラスを拡張します

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

件名がわかったら、Truth can reason at compile time about what propositions are known for that subject。 これにより、その特定のサブジェクトに固有の命題メソッドを宣言する値のラッパーを返すことができます。

たとえば、リストでアサートする場合、Truthcontains()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 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. Objectアサーション

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クラスには他のアサーションメソッドがあります。 それらすべてを見つけるには、Subject documentationを参照してください。

次のセクションでは、we are going to focus on the most relevant methods for each particular typeTruthがサポートします。 ただし、Subjectクラスのすべてのメソッドも適用できることに注意してください。

5.2. IntegerFloat,、およびDoubleアサーション

IntegerFloat,、および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. 配列表明

Arraysをチェックして、他の配列と等しいかどうかを確認できます。

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

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

@Test
public void whenCheckingComparable_thenAtLeast() {
    Comparable 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インスタンスが同等であるかどうかをテストすることもできます。

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

public class User implements Comparable {
    // ...

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

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

    assertThat(aList).contains(5);
}

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

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

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

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

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

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

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

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

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

    assertThat(aList).isOrdered(aComparator);
}

5.9. Mapアサーション

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

@Test
public void whenCheckingMap_thenContainsEntry() {
    Map 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 aMap = new HashMap<>();
    aMap.put("first", 1L);
    aMap.put("second", 2.0);
    aMap.put("third", 3f);

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

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

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

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

6. Java8アサーション

OptionalStreamは、Truthがサポートする唯一の2つのJava8タイプです。

6.1. Optionalアサーション

Optionalを検証するための3つの重要な方法があります。

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

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

    assertThat(anOptional).hasValue(1);
}

値が存在する場合:

@Test
public void whenCheckingJavaOptional_thenPresent() {
    Optional anOptional = Optional.of("example");

    assertThat(anOptional).isPresent();
}

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

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

    assertThat(anOptional).isEmpty();
}

6.2. Streamアサーション

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

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

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

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

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

7. グアバアサーション

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

7.1. Optionalアサーション

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

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

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

    assertThat(anOptional).isAbsent();
}

7.2. Multimapアサーション

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

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

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

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

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

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

7.3. Multisetアサーション

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

@Test
public void whenCheckingGuavaMultiset_thenExpectedCount() {
    TreeMultiset aMultiset = TreeMultiset.create();
    aMultiset.add("example", 10);

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

7.4. Tableアサーション

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

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

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

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

@Test
public void whenCheckingGuavaTable_thenContainsCell() {
    Table aTable = getDummyGuavaTable();

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

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

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つを拡張します

  • FailureStrategyとカスタム型のインスタンスの2つの引数を受け入れるコンストラクターを定義します

  • SubjectFactoryタイプのフィールドを宣言します。これは、Truthがカスタムサブジェクトのインスタンスを作成するために使用します。

  • カスタム型を受け入れる静的なassertThat()メソッドを実装します

  • テストアサーションAPIを公開します

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

public class UserSubject
  extends ComparableSubject {

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

    private static final
      SubjectFactory USER_SUBJECT_FACTORY
      = new SubjectFactory() {

        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で最も一般的なアサーションメソッドを紹介しました。

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