Testando com Hamcrest
1. Visão geral
Hamcrest é a estrutura bem conhecida usada para testes de unidade no ecossistema Java. É empacotado em JUnit e, simplesmente, usa predicados existentes - chamados de classes matcher - para fazer afirmações.
Neste tutorial, iremosexplore the Hamcrest APIe aprenderemos como tirar vantagem disso para escrever testes de unidade mais simples e intuitivos para nosso software.
2. Configuração do Hamcrest
Podemos usarHamcrest com maven adicionando a seguinte dependência ao nosso arquivopom.xml:
org.hamcrest
hamcrest-all
1.3
A versão mais recente desta biblioteca pode sempre ser encontradahere.
3. Um exemplo de teste
Hamcrest é comumente usado comjunite outras estruturas de teste para fazer asserções. Especificamente, em vez de usar os vários métodosassert dejunit, usamos apenas a instruçãoassertThat única da API com os correspondentes adequados.
Vejamos um exemplo que testa doisStrings para igualdade, independentemente do caso. Isso deve nos dar uma ideia clara sobre comoHamcrest se encaixa em um método de teste:
public class StringMatcherTest {
@Test
public void given2Strings_whenEqual_thenCorrect() {
String a = "foo";
String b = "FOO";
assertThat(a, equalToIgnoringCase(b));
}
}
Nas seções a seguir, daremos uma olhada em várias outras ofertas deHamcrest correspondentes comuns.
4. O MatcherObject
Hamcrest fornece matchers para fazer asserções em objetos Java arbitrários.
Para afirmar que o métodotoString de umObject retorna umString especificado:
@Test
public void givenBean_whenToStringReturnsRequiredString_thenCorrect(){
Person person=new Person("Barrack", "Washington");
String str=person.toString();
assertThat(person,hasToString(str));
}
Também podemos verificar se uma classe é uma subclasse de outra:
@Test
public void given2Classes_whenOneInheritsFromOther_thenCorrect(){
assertThat(Cat.class,typeCompatibleWith(Animal.class));
}
}
5. The Bean Matcher
Podemos usar o matcher Bean deHamcrest para inspecionar propriedades de um bean Java.
Suponha o seguinte beanPerson:
public class Person {
String name;
String address;
public Person(String personName, String personAddress) {
name = personName;
address = personAddress;
}
}
Podemos verificar se o bean tem a propriedadename assim:
@Test
public void givenBean_whenHasValue_thenCorrect() {
Person person = new Person("example", 25);
assertThat(person, hasProperty("name"));
}
Também podemos verificar sePerson tem a propriedadeaddress, inicializada em Nova York:
@Test
public void givenBean_whenHasCorrectValue_thenCorrect() {
Person person = new Person("example", "New York");
assertThat(person, hasProperty("address", equalTo("New York")));
}
Também podemos verificar se dois objetosPerson são construídos com os mesmos valores:
@Test
public void given2Beans_whenHavingSameValues_thenCorrect() {
Person person1 = new Person("example", "New York");
Person person2 = new Person("example", "New York");
assertThat(person1, samePropertyValuesAs(person2));
}
6. O MatcherCollection
Hamcrest fornece correspondências para inspecionarCollections.
Verificação simples para descobrir se umCollection está vazio:
@Test
public void givenCollection_whenEmpty_thenCorrect() {
List emptyList = new ArrayList<>();
assertThat(emptyList, empty());
}
Para verificar o tamanho de umCollection:
@Test
public void givenAList_whenChecksSize_thenCorrect() {
List hamcrestMatchers = Arrays.asList(
"collections", "beans", "text", "number");
assertThat(hamcrestMatchers, hasSize(4));
}
Também podemos usá-lo para afirmar que uma matriz tem um tamanho necessário:
@Test
public void givenArray_whenChecksSize_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat(hamcrestMatchers, arrayWithSize(4));
}
Para verificar se umCollection contém membros fornecidos, independentemente da ordem:
@Test
public void givenAListAndValues_whenChecksListForGivenValues_thenCorrect() {
List hamcrestMatchers = Arrays.asList(
"collections", "beans", "text", "number");
assertThat(hamcrestMatchers,
containsInAnyOrder("beans", "text", "collections", "number"));
}
Para afirmar ainda que os membrosCollection estão em determinada ordem:
@Test
public void givenAListAndValues_whenChecksListForGivenValuesWithOrder_thenCorrect() {
List hamcrestMatchers = Arrays.asList(
"collections", "beans", "text", "number");
assertThat(hamcrestMatchers,
contains("collections", "beans", "text", "number"));
}
Para verificar se uma matriz possui um único elemento:
@Test
public void givenArrayAndValue_whenValueFoundInArray_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat(hamcrestMatchers, hasItemInArray("text"));
}
Também podemos usar um comparador alternativo para o mesmo teste:
@Test
public void givenValueAndArray_whenValueIsOneOfArrayElements_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat("text", isOneOf(hamcrestMatchers));
}
Ou ainda podemos fazer o mesmo com um parceiro diferente da seguinte forma:
@Test
public void givenValueAndArray_whenValueFoundInArray_thenCorrect() {
String[] array = new String[] { "collections", "beans", "text",
"number" };
assertThat("beans", isIn(array));
}
Também podemos verificar se a matriz contém elementos fornecidos, independentemente da ordem:
@Test
public void givenArrayAndValues_whenValuesFoundInArray_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat(hamcrestMatchers,
arrayContainingInAnyOrder("beans", "collections", "number",
"text"));
}
Para verificar se a matriz contém elementos fornecidos, mas na ordem especificada:
@Test
public void givenArrayAndValues_whenValuesFoundInArrayInOrder_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat(hamcrestMatchers,
arrayContaining("collections", "beans", "text", "number"));
}
Quando nossoCollection é umMap,, podemos usar os seguintes matchers nessas respectivas funções:
Para verificar se contém uma determinada chave:
@Test
public void givenMapAndKey_whenKeyFoundInMap_thenCorrect() {
Map map = new HashMap<>();
map.put("blogname", "example");
assertThat(map, hasKey("blogname"));
}
e um determinado valor:
@Test
public void givenMapAndValue_whenValueFoundInMap_thenCorrect() {
Map map = new HashMap<>();
map.put("blogname", "example");
assertThat(map, hasValue("example"));
}
e finalmente uma entrada específica (chave, valor):
@Test
public void givenMapAndEntry_whenEntryFoundInMap_thenCorrect() {
Map map = new HashMap<>();
map.put("blogname", "example");
assertThat(map, hasEntry("blogname", "example"));
}
7. O MatcherNumber
Os matchersNumber são usados para realizar asserções sobre variáveis da classeNumber.
Para verificar a condição degreaterThan:
@Test
public void givenAnInteger_whenGreaterThan0_thenCorrect() {
assertThat(1, greaterThan(0));
}
Para verificar a condiçãogreaterThan ouequalTo:
@Test
public void givenAnInteger_whenGreaterThanOrEqTo5_thenCorrect() {
assertThat(5, greaterThanOrEqualTo(5));
}
Para verificar a condição delessThan:
@Test
public void givenAnInteger_whenLessThan0_thenCorrect() {
assertThat(-1, lessThan(0));
}
Para verificar a condiçãolessThan ouequalTo:
@Test
public void givenAnInteger_whenLessThanOrEqTo5_thenCorrect() {
assertThat(-1, lessThanOrEqualTo(5));
}
Para verificar a condição decloseTo:
@Test
public void givenADouble_whenCloseTo_thenCorrect() {
assertThat(1.2, closeTo(1, 0.5));
}
Vamos prestar atenção ao último matcher,closeTo. O primeiro argumento, o operando, é aquele com o qual o destino é comparado e o segundo argumento é o desvio permitido do operando. Isso significa que se o alvo é operando + desvio ou operando-desvio, então o teste será aprovado.
8. O Correspondente de Texto
A afirmação sobreStrings ficou mais fácil, organizada e intuitiva com os matchers de texto deHamcrest. Vamos dar uma olhada neles nesta seção.
Para verificar se aString está vazio:
@Test
public void givenString_whenEmpty_thenCorrect() {
String str = "";
assertThat(str, isEmptyString());
}
Para verificar se aString está vazio ounull:
@Test
public void givenString_whenEmptyOrNull_thenCorrect() {
String str = null;
assertThat(str, isEmptyOrNullString());
}
Para verificar a igualdade de doisStrings enquanto ignora o espaço em branco:
@Test
public void given2Strings_whenEqualRegardlessWhiteSpace_thenCorrect() {
String str1 = "text";
String str2 = " text ";
assertThat(str1, equalToIgnoringWhiteSpace(str2));
}
Também podemos verificar a presença de uma ou mais sequências secundárias em um determinadoString em uma determinada ordem:
@Test
public void givenString_whenContainsGivenSubstring_thenCorrect() {
String str = "calligraphy";
assertThat(str, stringContainsInOrder(Arrays.asList("call", "graph")));
}
Finalmente, podemos verificar a igualdade de doisStrings independentemente do caso:
@Test
public void given2Strings_whenEqual_thenCorrect() {
String a = "foo";
String b = "FOO";
assertThat(a, equalToIgnoringCase(b));
}
9. A API principal
A API principal deHamcrest deve ser usada por provedores de estrutura de terceiros. No entanto, ele nos oferece ótimas construções para tornar nossos testes de unidade mais legíveis e também alguns correspondentes de núcleo que podem ser usados com a mesma facilidade.
Legibilidade com a construçãois em um matcher:
@Test
public void given2Strings_whenIsEqualRegardlessWhiteSpace_thenCorrect() {
String str1 = "text";
String str2 = " text ";
assertThat(str1, is(equalToIgnoringWhiteSpace(str2)));
}
A construçãois em um tipo de dados simples:
@Test
public void given2Strings_whenIsEqual_thenCorrect() {
String str1 = "text";
String str2 = "text";
assertThat(str1, is(str2));
}
Negação com a construçãonot em um matcher:
@Test
public void given2Strings_whenIsNotEqualRegardlessWhiteSpace_thenCorrect() {
String str1 = "text";
String str2 = " texts ";
assertThat(str1, not(equalToIgnoringWhiteSpace(str2)));
}
A construçãonot em um tipo de dados simples:
@Test
public void given2Strings_whenNotEqual_thenCorrect() {
String str1 = "text";
String str2 = "texts";
assertThat(str1, not(str2));
}
Verifique seString contém uma determinada substring:
@Test
public void givenAStrings_whenContainsAnotherGivenString_thenCorrect() {
String str1 = "calligraphy";
String str2 = "call";
assertThat(str1, containsString(str2));
}
Verifique seString começa com a subcadeia fornecida:
@Test
public void givenAString_whenStartsWithAnotherGivenString_thenCorrect() {
String str1 = "calligraphy";
String str2 = "call";
assertThat(str1, startsWith(str2));
}
Verifique se umString termina com a subcadeia fornecida:
@Test
public void givenAString_whenEndsWithAnotherGivenString_thenCorrect() {
String str1 = "calligraphy";
String str2 = "phy";
assertThat(str1, endsWith(str2));
}
Verifique se doisObjects são da mesma instância:
@Test
public void given2Objects_whenSameInstance_thenCorrect() {
Cat cat=new Cat();
assertThat(cat, sameInstance(cat));
}
Verifique seObject é uma instância de uma determinada classe:
@Test
public void givenAnObject_whenInstanceOfGivenClass_thenCorrect() {
Cat cat=new Cat();
assertThat(cat, instanceOf(Cat.class));
}
Verifique se todos os membros de umCollection atendem a uma condição:
@Test
public void givenList_whenEachElementGreaterThan0_thenCorrect() {
List list = Arrays.asList(1, 2, 3);
int baseCase = 0;
assertThat(list, everyItem(greaterThan(baseCase)));
}
Verifique se aString não énull:
@Test
public void givenString_whenNotNull_thenCorrect() {
String str = "notnull";
assertThat(str, notNullValue());
}
As condições da cadeia juntas, o teste passa quando o destino atende a qualquer uma das condições, semelhante ao OR lógico:
@Test
public void givenString_whenMeetsAnyOfGivenConditions_thenCorrect() {
String str = "calligraphy";
String start = "call";
String end = "foo";
assertThat(str, anyOf(startsWith(start), containsString(end)));
}
Condições da cadeia juntas, o teste passa somente quando o destino atende a todas as condições, semelhante ao AND lógico:
@Test
public void givenString_whenMeetsAllOfGivenConditions_thenCorrect() {
String str = "calligraphy";
String start = "call";
String end = "phy";
assertThat(str, allOf(startsWith(start), endsWith(end)));
}
10. Um Matcher personalizado
Podemos definir nosso próprio matcher estendendoTypeSafeMatcher. Nesta seção, criaremos um combinador personalizado que permite que um teste seja aprovado apenas quando o destino for um número inteiro positivo.
public class IsPositiveInteger extends TypeSafeMatcher {
public void describeTo(Description description) {
description.appendText("a positive integer");
}
@Factory
public static Matcher isAPositiveInteger() {
return new IsPositiveInteger();
}
@Override
protected boolean matchesSafely(Integer integer) {
return integer > 0;
}
}
Precisamos apenas implementar o métodomatchSafely, que verifica se o alvo é realmente um número inteiro positivo, e o métododescribeTo, que produz uma mensagem de falha caso o teste não seja aprovado.
Aqui está um teste que usa nosso novo combinador personalizado:
@Test
public void givenInteger_whenAPositiveValue_thenCorrect() {
int num = 1;
assertThat(num, isAPositiveInteger());
}
e aqui está uma mensagem de falha que recebemos desde que passamos em um número inteiro não positivo:
java.lang.AssertionError: Expected: a positive integer but: was <-1>
11. Conclusão
Neste tutorial, temosexplored the Hamcrest APIe aprendemos como podemos escrever testes de unidade melhores e mais sustentáveis com ele.
A implementação completa de todos esses exemplos e snippets de códigocan be found in my Hamcrest github project.