Перейти к основному содержимому

Тестирование с помощью Google Truth

· 12 мин. чтения

1. Обзор

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

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

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

Во- первых, нам нужно добавить правда и правда-java8-расширение в наш pom.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>

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

3. Введение

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

  • Стандартная Java — примитивы, массивы, строки, объекты, коллекции, броски, классы и т. д.
  • Java 8необязательные и потоковые экземпляры
  • Guavaнеобязательные объекты , Multimap , Multiset и Table .
  • Пользовательские типы — путем расширения класса Subject , как мы увидим позже.

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

Когда предмет известен, Truth может рассуждать во время компиляции о том, какие предложения известны для этого предмета . Это позволяет ему возвращать обертки вокруг нашего значения, которые объявляют методы предложения, специфичные для этого конкретного предмета.

Например, при утверждении списка 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<String> emails
= Arrays.asList("contact@foreach.com", "staff@foreach.com");

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. Утверждения объекта

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 .

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

5.2. Целочисленные , плавающие и двойные утверждения

Экземпляры 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() :

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

assertThat(aBoolean).isTrue();
}

5.5. Строковые утверждения

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

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

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

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

5.6. Утверждения массива

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

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

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

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

public class User implements Comparable<User> {
// ...

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 заключаются в том, что он содержит некоторый элемент:

@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. Утверждения карты

В дополнение к утверждению, что экземпляр карты пуст или нет, или имеет определенный размер; мы можем проверить, есть ли у него конкретная запись:

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

или если он имеет те же записи, что и другая карта :

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

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

@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. Утверждения класса

Есть только один важный метод для утверждений класса , с помощью которого мы можем проверить, можно ли присвоить класс другому:

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

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

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

Необязательный и Stream — единственные два типа Java 8, которые поддерживает Truth .

6.1. Необязательные утверждения

Существует три важных метода проверки необязательного .

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

@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("ForEach");

assertThat(anOptional).isPresent();
}

или если значение отсутствует:

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

assertThat(anOptional).isEmpty();
}

6.2. Потоковые утверждения

Утверждения для Stream очень похожи на утверждения для Iterable .

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

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

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

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

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

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

7.1. Необязательные утверждения

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

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

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

assertThat(anOptional).isAbsent();
}

7.2. Утверждения мультикарты

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

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

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

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

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

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

7.4. Утверждения таблицы

Помимо проверки ее размера или того, где она пуста, мы можем проверить таблицу , чтобы убедиться, что она содержит конкретное сопоставление для данной строки и столбца:

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

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

или если он содержит определенную ячейку:

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

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

Кроме того, мы можем проверить, содержит ли он заданную строку, столбец или значение. См. исходный код для соответствующих тестовых примеров.

8. Пользовательские сообщения об ошибках и ярлыки

Когда утверждение терпит неудачу, 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.foreach.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.foreach.testing.truth.User@ae805d5e>) is null

9. Расширения

Расширение Truth означает, что мы можем добавить поддержку пользовательских типов. Для этого нам нужно создать класс, который:

  • расширяет класс Subject или один из его подклассов
  • определяет конструктор, который принимает два аргумента — FailureStrategy и экземпляр нашего пользовательского типа.
  • объявляет поле типа 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, настраиваемые сообщения об ошибках и расширенную истину с настраиваемыми темами.

Как всегда, полный исходный код этой статьи можно найти на Github .