Testando com o Google Truth
1. Visão geral
Truth é umfluent and flexible open-source testing framework designed to make test assertions and failure messages more readable.
Neste artigo, vamos explorar os principais recursos da estruturaTruth e implementar exemplos para mostrar seus recursos.
2. Dependências do Maven
Primeiro, precisamos adicionartruthetruth-java8-extension ao nossopom.xml:
com.google.truth
truth
0.32
com.google.truth.extensions
truth-java8-extension
0.32
test
Você pode encontrar as versões mais recentes detruthetruth-java8-extension no Maven Central.
3. Introdução
Truth nos permite escrever asserções legíveis e mensagens de falha para uma variedade de classes:
-
Standard Java - primitivos, arrays, strings, objetos, coleções, jogáveis, classes, etc.
-
Java 8 -Optional eStream instâncias
-
Guava - objetosOptional,Multimap,Multiset eTable
-
Custom types - estendendo a classeSubject, como veremos mais tarde
Por meio das classesTrutheTruth8, a biblioteca fornece métodos utilitários para escrever asserções que funcionam emsubject, que é o valor ou objeto em teste.
Assim que o assunto for conhecido,Truth can reason at compile time about what propositions are known for that subject. Isso permite retornar invólucros em torno de nosso valor que declaram métodos de proposição específicos para esse assunto em particular.
Por exemplo, ao declarar em uma lista,Truth retorna uma instânciaIterableSubject definindo métodos comocontains()econtainsAnyOf(), entre outros. Ao afirmar em umMap, ele retorna umMapSubject que declara métodos comocontainsEntry()econtainsKey().
4. Começando
Para começar a escrever afirmações, vamos primeiro importar os pontos de entrada deTruth:
import static com.google.common.truth.Truth.*;
import static com.google.common.truth.Truth8.*;
Agora, vamos escrever uma classe simples que usaremos em alguns dos exemplos a seguir:
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
}
Observe o métodoequals() personalizado, no qual afirmamos que dois objetosUser são iguais se seus nomes forem.
5. Asserções Java padrão
Nesta seção, veremos exemplos detalhados de como escrever asserções de teste para tipos Java padrão.
5.1. Object afirmações
Truth fornece o wrapperSubject para executar asserções em objetos. Subject também é o pai de todos os outros wrappers na biblioteca e declara métodos para determinar se umObject, no nosso caso umUser, é igual a outro objeto:
@Test
public void whenComparingUsers_thenEqual() {
User aUser = new User("John Doe");
User anotherUser = new User("John Doe");
assertThat(aUser).isEqualTo(anotherUser);
}
ou se for igual a um determinado objeto em uma lista:
@Test
public void whenComparingUser_thenInList() {
User aUser = new User();
assertThat(aUser).isIn(Arrays.asList(1, 3, aUser, null));
}
ou se não for:
@Test
public void whenComparingUser_thenNotInList() {
// ...
assertThat(aUser).isNotIn(Arrays.asList(1, 3, "Three"));
}
se é nulo ou não:
@Test
public void whenComparingUser_thenIsNull() {
User aUser = null;
assertThat(aUser).isNull();
}
@Test
public void whenComparingUser_thenNotNull() {
User aUser = new User();
assertThat(aUser).isNotNull();
}
ou se for uma instância de uma classe particular:
@Test
public void whenComparingUser_thenInstanceOf() {
// ...
assertThat(aUser).isInstanceOf(User.class);
}
Existem outros métodos de asserção na classeSubject. Para descobrir todos eles, consulteSubject documentation.
Nas seções a seguir,we are going to focus on the most relevant methods for each particular typeTruth suporta. No entanto, lembre-se de que todos os métodos da classeSubject também podem ser aplicados.
5.2. Integer,Float,eDouble afirmações
As instâncias deInteger,Float, eDouble podem ser comparadas quanto à igualdade:
@Test
public void whenComparingInteger_thenEqual() {
int anInt = 10;
assertThat(anInt).isEqualTo(10);
}
se eles são maiores:
@Test
public void whenComparingFloat_thenIsBigger() {
float aFloat = 10.0f;
assertThat(aFloat).isGreaterThan(1.0f);
}
ou menor:
@Test
public void whenComparingDouble_thenIsSmaller() {
double aDouble = 10.0f;
assertThat(aDouble).isLessThan(20.0);
}
Além disso, as instâncias de, Float eDouble também podem ser verificadas para ver se estão dentro de uma precisão esperada ou não:
@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 afirmações
Além das afirmações comuns, esse tipo pode ser comparado ignorando sua escala:
@Test
public void whenComparingBigDecimal_thenEqualIgnoringScale() {
BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3);
assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0));
}
5.4. Boolean afirmações
Apenas dois métodos relevantes são fornecidos,isTrue()eisFalse():
@Test
public void whenCheckingBoolean_thenTrue() {
boolean aBoolean = true;
assertThat(aBoolean).isTrue();
}
5.5. String afirmações
Podemos testar se umString começa com um texto específico:
@Test
public void whenCheckingString_thenStartsWith() {
String aString = "This is a string";
assertThat(aString).startsWith("This");
}
Além disso, podemos verificar se a string contém uma determinada String, se termina com um valor esperado ou se está vazia. Casos de teste para esses e outros métodos estão disponíveis no código-fonte.
5.6. Asserções de matriz
Podemos verificarArrays para ver se eles são iguais a outros arrays:
@Test
public void whenComparingArrays_thenEqual() {
String[] firstArrayOfStrings = { "one", "two", "three" };
String[] secondArrayOfStrings = { "one", "two", "three" };
assertThat(firstArrayOfStrings).isEqualTo(secondArrayOfStrings);
}
ou se estiverem vazios:
@Test
public void whenCheckingArray_thenEmpty() {
Object[] anArray = {};
assertThat(anArray).isEmpty();
}
5.7. Comparable afirmações
Além de testar se umComparable é maior ou menor que outra instância, podemos verificar se eles têm pelo menos um determinado valor:
@Test
public void whenCheckingComparable_thenAtLeast() {
Comparable aComparable = 5;
assertThat(aComparable).isAtLeast(1);
}
Além disso, podemos testar se eles estão dentro de um intervalo específico:
@Test
public void whenCheckingComparable_thenInRange() {
// ...
assertThat(aComparable).isIn(Range.closed(1, 10));
}
ou em uma lista específica:
@Test
public void whenCheckingComparable_thenInList() {
// ...
assertThat(aComparable).isIn(Arrays.asList(4, 5, 6));
}
Também podemos testar se duas instâncias deComparable são equivalentes de acordo com o métodocompareTo() da classe.
Primeiro, vamos modificar nossa classeUser para implementar a interfaceComparable:
public class User implements Comparable {
// ...
public int compareTo(User o) {
return this.getName().compareToIgnoreCase(o.getName());
}
}
Agora, vamos afirmar que dois usuários com o mesmo nome são equivalentes:
@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 afirmações
Além de declarar o tamanho de uma instânciaIterable, esteja ela vazia ou não tenha duplicatas, as afirmações mais comuns em umaIterable são que ela contém algum elemento:
@Test
public void whenCheckingIterable_thenContains() {
List aList = Arrays.asList(4, 5, 6);
assertThat(aList).contains(5);
}
que contém qualquer elemento de outroIterable:
@Test
public void whenCheckingIterable_thenContainsAnyInList() {
List aList = Arrays.asList(1, 2, 3);
assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10));
}
e que o sujeito tenha os mesmos elementos, na mesma ordem, como outro:
@Test
public void whenCheckingIterable_thenContainsExactElements() {
List aList = Arrays.asList("10", "20", "30");
List anotherList = Arrays.asList("10", "20", "30");
assertThat(aList)
.containsExactlyElementsIn(anotherList)
.inOrder();
}
e se for pedido usando um comparador personalizado:
@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 afirmações
Além de afirmar que uma instânciaMap está vazia ou não, ou tem um tamanho específico; podemos verificar se ele tem uma entrada específica:
@Test
public void whenCheckingMap_thenContainsEntry() {
Map aMap = new HashMap<>();
aMap.put("one", 1L);
assertThat(aMap).containsEntry("one", 1L);
}
se tiver uma chave específica:
@Test
public void whenCheckingMap_thenContainsKey() {
// ...
assertThat(map).containsKey("one");
}
ou se tiver as mesmas entradas que outroMap:
@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 afirmações
Apenas dois métodos importantes são fornecidos para objetosException.
Podemos escrever afirmações endereçadas à causa da exceção:
@Test
public void whenCheckingException_thenInstanceOf() {
Exception anException
= new IllegalArgumentException(new NumberFormatException());
assertThat(anException)
.hasCauseThat()
.isInstanceOf(NumberFormatException.class);
}
ou à sua mensagem:
@Test
public void whenCheckingException_thenCauseMessageIsKnown() {
Exception anException
= new IllegalArgumentException("Bad value");
assertThat(anException)
.hasMessageThat()
.startsWith("Bad");
}
5.11. Class afirmações
Existe apenas um método importante paraClass asserções com o qual podemos testar se uma classe pode ser atribuída a outra:
@Test
public void whenCheckingClass_thenIsAssignable() {
Class aClass = Double.class;
assertThat(aClass).isAssignableTo(Number.class);
}
6. Asserções Java 8
Optional eStream são os únicos dois tipos Java 8 queTruth suporta.
6.1. Optional afirmações
Existem três métodos importantes para verificar umOptional.
Podemos testar se ele possui um valor específico:
@Test
public void whenCheckingJavaOptional_thenHasValue() {
Optional anOptional = Optional.of(1);
assertThat(anOptional).hasValue(1);
}
se o valor estiver presente:
@Test
public void whenCheckingJavaOptional_thenPresent() {
Optional anOptional = Optional.of("example");
assertThat(anOptional).isPresent();
}
ou se o valor não estiver presente:
@Test
public void whenCheckingJavaOptional_thenEmpty() {
Optional anOptional = Optional.empty();
assertThat(anOptional).isEmpty();
}
6.2. Stream afirmações
Asserções para aStream são muito semelhantes às deIterable.
Por exemplo, podemos testar se um determinadoStream contém todos os objetos de umIterable na mesma ordem:
@Test
public void whenCheckingStream_thenContainsInOrder() {
Stream anStream = Stream.of(1, 2, 3);
assertThat(anStream)
.containsAllOf(1, 2, 3)
.inOrder();
}
Para obter mais exemplos, consulte a seçãoIterable Asserções.
7. Guava Assertions
Nesta seção, veremos exemplos de asserções para os tipos Guava suportados emTruth.
7.1. Optional afirmações
Existem também três métodos de afirmação importantes para um GuavaOptional. Os métodoshasValue()eisPresent() se comportam exatamente como com um Java 8Optional.
Mas em vez deisEmpty() para afirmar que umOptional não está presente, usamosisAbsent():
@Test
public void whenCheckingGuavaOptional_thenIsAbsent() {
Optional anOptional = Optional.absent();
assertThat(anOptional).isAbsent();
}
7.2. Multimap afirmações
As asserçõesMultimape padrãoMap são muito semelhantes.
Uma diferença notável é que podemos obter os vários valores de uma chave emMultimape fazer afirmações sobre esses valores.
Aqui está um exemplo que testa se os valores da chave "um" têm um tamanho de dois:
@Test
public void whenCheckingGuavaMultimap_thenExpectedSize() {
Multimap aMultimap = ArrayListMultimap.create();
aMultimap.put("one", 1L);
aMultimap.put("one", 2.0);
assertThat(aMultimap)
.valuesForKey("one")
.hasSize(2);
}
Para obter mais exemplos, consulte a seçãoMap Asserções.
7.3. Multiset afirmações
Asserções para objetosMultiset incluem aquelas para umIterablee um método extra para verificar se uma chave tem um determinado número de ocorrências:
@Test
public void whenCheckingGuavaMultiset_thenExpectedCount() {
TreeMultiset aMultiset = TreeMultiset.create();
aMultiset.add("example", 10);
assertThat(aMultiset).hasCount("example", 10);
}
7.4. Table afirmações
Além de verificar seu tamanho ou onde está vazio, podemos verificar umTable para verificar se ele contém um mapeamento específico para uma determinada linha e coluna:
@Test
public void whenCheckingGuavaTable_thenContains() {
Table aTable = TreeBasedTable.create();
aTable.put("firstRow", "firstColumn", "example");
assertThat(aTable).contains("firstRow", "firstColumn");
}
ou se contiver uma célula específica:
@Test
public void whenCheckingGuavaTable_thenContainsCell() {
Table aTable = getDummyGuavaTable();
assertThat(aTable).containsCell("firstRow", "firstColumn", "example");
}
Além disso, podemos verificar se ele contém uma determinada linha, coluna ou valor. Veja o código fonte para os casos de teste relevantes.
8. Mensagens e rótulos de falha personalizados
Quando uma declaração falha,Truth exibe mensagens muito legíveis, denotando exatamente o que deu errado. No entanto, às vezes é necessário adicionar mais informações a essas mensagens para fornecer mais detalhes sobre o que aconteceu.
Truth nos permite personalizar essas mensagens de falha:
@Test
public void whenFailingAssertion_thenCustomMessage() {
assertWithMessage("TEST-985: Secret user subject was NOT null!")
.that(new User())
.isNull();
}
Depois de executar o teste, obtemos a seguinte saída:
TEST-985: Secret user subject was NOT null!:
Not true that <[email protected]> is null
Além disso, podemos adicionar um rótulo personalizado que é exibido antes do assunto em mensagens de erro. Isso pode ser útil quando um objeto não tem uma representação de string útil:
@Test
public void whenFailingAssertion_thenMessagePrefix() {
User aUser = new User();
assertThat(aUser)
.named("User [%s]", aUser.getName())
.isNull();
}
Se executarmos o teste, podemos ver a seguinte saída:
Not true that User [John Doe]
(<[email protected]>) is null
9. Extensões
EstenderTruth significa que podemos adicionar suporte para tipos personalizados. Para fazer isso, precisamos criar uma classe que:
-
estende a classeSubject ou uma de suas subclasses
-
define um construtor que aceita dois argumentos - umFailureStrategye uma instância do nosso tipo personalizado
-
declara um campo do tipoSubjectFactory, queTruth usará para criar instâncias de nosso assunto personalizado
-
implementa um método estáticoassertThat() que aceita nosso tipo personalizado
-
expõe nossa API de asserção de teste
Agora que sabemos como estenderTruth, vamos criar uma classe que adiciona suporte para objetos do tipoUser:
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());
}
}
Agora, podemos importar estaticamente o métodoassertThat() do nosso assunto personalizado e escrever alguns testes:
@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. Conclusão
Neste tutorial, exploramos as possibilidades queTruth nos dá para escrever testes mais legíveis e mensagens de falha.
Apresentamos os métodos de asserção mais populares para os tipos Java e Guava suportados, mensagens de falha personalizadas eTruth estendidos com assuntos personalizados.
Como sempre, o código-fonte completo deste artigo pode ser encontradoover on Github.