Тестирование с Google Truth

Тестирование с Google Truth

1. обзор

Truth - этоfluent and flexible open-source testing framework designed to make test assertions and failure messages more readable.

В этой статье мы рассмотрим ключевые особенности фреймворкаTruth и реализуем примеры, чтобы продемонстрировать его возможности.

2. Maven Зависимости

Во-первых, нам нужно добавитьtruth иtruth-java8-extension к нашемуpom.xml:


    com.google.truth
    truth
    0.32


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

Вы можете найти последние версииtruth иtruth-java8-extension на Maven Central.

3. Вступление

Truth позволяет нам писать удобные для чтения утверждения и сообщения об ошибках для различных классов:

  • Standard Java - примитивы, массивы, строки, объекты, коллекции, бросаемые объекты, классы и т. д.

  • Java 8 - экземплярыOptional иStream

  • Guava - объектыOptional,Multimap,Multiset иTable

  • Custom types - путем расширения классаSubject, как мы увидим позже

Через классыTruth иTruth8 библиотека предоставляет служебные методы для написания утверждений, которые работают сsubject, то есть с тестируемым значением или объектом.

Как только субъект известен,Truth can reason at compile time about what propositions are known for that subject. Это позволяет ему возвращать обертки вокруг нашего значения, которые объявляют методы предложения, специфичные для этой конкретной темы.

Например, при утверждении в спискеTruth возвращает экземплярIterableSubject, определяющий такие методы, какcontains() иcontainsAnyOf(), среди прочих. При утверждении наMap он возвращаетMapSubject, который объявляет такие методы, какcontainsEntry() иcontainsKey().

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(), в котором мы заявляем, что два объекта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"));
}

если он пуст или нет:

@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. Утверждения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():

@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 Assertions

Мы можем проверить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));
}

Мы также можем проверить эквивалентность двух экземпляровComparable в соответствии с методом классаcompareTo().

Во-первых, давайте изменим наш классUser, чтобы реализовать интерфейсComparable:

public class User implements Comparable {
    // ...

    public int compareTo(User o) {
        return this.getName().compareToIgnoreCase(o.getName());
    }
}

Теперь предположим, что два пользователя с одинаковым именем эквивалентны:

@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 предусмотрены только два важных метода.

Мы можем написать утверждения, адресованные причине исключения:

@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, с помощью которого мы можем проверить, назначается ли один класс другому:

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

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

6. Утверждения Java 8

Optional иStream - единственные два типа Java 8, которые поддерживаетTruth.

6.1. Optional Утверждения

Есть три важных метода проверкиOptional.

Мы можем проверить, имеет ли оно конкретное значение:

@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.

Например, мы можем проверить, содержит ли конкретныйStream все объектыIterable в том же порядке:

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

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

Дополнительные примеры см. В разделе УтвержденияIterable.

7. Утверждения Гуавы

В этом разделе мы увидим примеры утверждений для поддерживаемых типов Guava вTruth.

7.1. Optional Утверждения

Также есть три важных метода утверждения для GuavaOptional. МетодыhasValue() иisPresent() ведут себя точно так же, как в Java 8Optional.

Но вместоisEmpty(), чтобы утверждать, чтоOptional отсутствует, мы используемisAbsent():

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

    assertThat(anOptional).isAbsent();
}

7.2. Multimap Утверждения

УтвержденияMultimap и стандартныеMap очень похожи.

Одно заметное отличие состоит в том, что мы можем получить несколько значений ключа в пределахMultimap и сделать утверждения по этим значениям.

Вот пример, который проверяет, имеют ли значения ключа «один» размер два:

@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 и один дополнительный метод проверки наличия у ключа определенного количества вхождений:

@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 или один из его подклассов

  • определяет конструктор, который принимает два аргумента -FailureStrategy и экземпляр нашего настраиваемого типа

  • объявляет поле типа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.