Teste de unidade com listas em Java

Para você que já trabalha com testes unitários ou ainda não faz uso de tal prática, neste post irei mostrar uma maneira amigável de elaborar testes com listas em Java.

Diariamente quando estamos desenvolvendo orientado a testes utilizando práticas como o TDD, nos deparamos com a missão de validar se um elemento especifico de algum objeto esta contido em uma lista, ou se os objetos da lista conferem com o esperado.

Em cursos ou até em algumas literaturas recomenda-se  para testes em Java utilizando listas, validar o tamanho da lista e que percorra os elementos da lista para ter a certeza de que estão contidos na mesma, como o exemplo abaixo:

@Test
public void deve_inserir_itens_de_compra_no_carrinho() throws Exception {
    int tamanhoEsperado = 2;
    String nomeDoItem1 = "Arroz";
    String nomeDoItem2 = "Feijão";
    Item item1 = CriadorDeItem.novo().comNome(nomeDoItem1).gerar();
    Item item2 = CriadorDeItem.novo().comNome(nomeDoItem2).gerar();

    carrinhoDeCompras.adicionarItens(Arrays.asList(item1, item2));

    assertEquals(tamanhoEsperado, carrinhoDeCompras.obterItens().size());
    assertTrue(verificaSeOsNomesDosItensSaoIguaisAoEsperado(nomeDoItem1, nomeDoItem2, carrinhoDeCompras));
}

private boolean verificaSeOsNomesDosItensSaoIguaisAoEsperado(String nomeDoItem1, String nomeDoItem2, CarrinhoDeCompras carrinhoDeCompras) {
    return nomeDoItem1.equals(carrinhoDeCompras.obterItens().get(0).getNome()) && nomeDoItem2.equals(carrinhoDeCompras.obterItens().get(1).getNome());
}

Observe que nesta abordagem utilizamos dois “asserts” um para avaliarmos se o tamanho da lista é o esperado e outro para verificar se a lista contém realmente os objetos inseridos, com a ajuda de um método. Algumas literaturas recomendam o uso de dois ou mais “asserts” para testes com listas, contudo ao utilizar o framework hamcrest conseguimos testar nossa lista com o uso de um único “assert” deixando o teste mais legível e de uma leitura amigável, observe o exemplo logo a seguir:

@Test
public void deve_inserir_itens_de_compra_no_carrinho() throws Exception {
    String nomeDoItem1 = "Arroz";
    String nomeDoItem2 = "Feijão";
    Item item1 = CriadorDeItem.novo().comNome(nomeDoItem1).gerar();
    Item item2 = CriadorDeItem.novo().comNome(nomeDoItem2).gerar();

    carrinhoDeCompras.adicionarItens(Arrays.asList(item1, item2));

    MatcherAssert.assertThat(carrinhoDeCompras.obterItens(), Matchers.contains(item1, item2));
}

Veja como o teste ficou menos verboso e mais elegante com apenas um assert. Vale ressaltar que na chamada do matchers contains() a ordem dos itens tem que ser a mesma da lista comparada, logo se eu colocasse

MatcherAssert.assertThat(carrinhoDeCompras.obterItens(), Matchers.contains(item2, item1));

, o teste não iria passar, pois a ordem esta inversa.

Para que esta “mágica” acontecesse utilizamos o Hamcrest, que é um framework para testes. O hamcrest permite a verificação de condições em seu código através de classes Matchers nele existentes. O hamcrest tem o objetivo de fazer testes mais legível possível. Saiba mais sobre o framework clicando aqui.

Muito bem! Vamos ver mais um exemplo de como utilizar o hamcrest e testes com listas:

Digamos que você precise saber se a lista em questão possui um elemento especifico, talvez seu código seria algo parecido com o exemplo abaixo:

@Test
public void deve_buscar_um_item_especifico_em_um_carrinho_de_compras() throws Exception {
    String arroz = "Arroz";
    String feijao = "Feijão";
    String refrigerante = "Refrigerante";
    Item item1 = CriadorDeItem.novo().comNome(arroz).gerar();
    Item item2 = CriadorDeItem.novo().comNome(feijao).gerar();
    Item item3 = CriadorDeItem.novo().comNome(refrigerante).gerar();

    carrinhoDeCompras.adicionarItens(Arrays.asList(item1, item2, item3));

    assertEquals(3, carrinhoDeCompras.obterItens().size());
    assertTrue(procurarItemNaLista(carrinhoDeCompras, item2));
}

private boolean procurarItemNaLista(CarrinhoDeCompras carrinhoDeCompras, Item itemASerProcurado) {
    for (Item item : carrinhoDeCompras.obterItens()) {
        if(itemASerProcurado.getNome().equals(item.getNome()))
            return true;
        }
        return false;
    }

Novamente se implementarmos conforme as praticas convencionais, teríamos que verificar o tamanho da lista e fazer um método para percorrer a lista e verificar se o elemento procurado está contido nela. Você poderia reduzir o número de linhas utilizando “stream” e “expressões lambdas” em vez do “foreach” eu falo um pouco sobre como utiliza-las em um outro post, você pode ver clicando aqui.

E como ficaria utilizando hamcrest ?

@Test
public void deve_buscar_um_item_especifico_em_um_carrinho_de_compras() throws Exception {
    String arroz = "Arroz";
    String feijao = "Feijão";
    String refrigerante = "Refrigerante";
    Item item1 = CriadorDeItem.novo().comNome(arroz).gerar();
    Item item2 = CriadorDeItem.novo().comNome(feijao).gerar();
    Item item3 = CriadorDeItem.novo().comNome(refrigerante).gerar();

    carrinhoDeCompras.adicionarItens(Arrays.asList(item1, item2, item3));

    MatcherAssert.assertThat(carrinhoDeCompras.obterItens(), hasItem(item2));
}

Ficou mais claro não acha ?
Agora utilizamos o matchers hasItem() que verifica se algum item está contido em alguma lista.

Aqui está apenas a ponta do iceberg, o framework hamcrest possui muitos outros matchers que facilitam e deixam seu teste mais legível. Para que você possa descobrir um pouco mais deixo para vocês um outro link clicando aqui para que você possa estudar o framework.

Pessoal é isto, espero ter te ajudado nos testes com listas em Java que nem sempre são intuitivos e fáceis para construir um assert. Se você encontrou alguma dificuldade, ou está com alguma dúvida deixe um comentário que ficarei feliz em te ajudar.

Até a próxima e que a força esteja com você… \o/

 

OPERAÇÕES BÁSICAS UTILIZANDO STREAM NO JAVA

Quem nunca se deparou com uma tarefa diária de busca em uma coleção, impressão dos valores contidos em uma lista ou ainda com uma ordenação qualquer? Com as novas funcionalidades disponibilizados no Java 8 como expressões lambdas e a chegada da nova interface fluente Stream, utilizar esses novos recursos ficou fácil e agiliza  algumas operações básicas que realizamos em nosso dia a dia. Neste post vou compartilhar com vocês algumas dicas sobre o uso dessa nova API.

Para efeito didático observe o trecho de código a seguir:

Conta conta1 = new Conta(65453, 600.50, "Jorge");
Conta conta2 = new Conta(42156, 2500.0, "Luiz");
Conta conta3 = new Conta(88951, 1000.0, "Gomes");
Conta conta4 = new Conta(21583, 99999.0, "Silva");
Conta conta5 = new Conta(63954, 9000.0, "João");

listaDeContas = Arrays.asList(conta1, conta2, conta3, conta4, conta5);

Temos uma lista de contas bancárias com o número da conta, saldo e o nome do titular respectivamente.

1. IMPRESSÃO

Para realizar a impressão de uma lista no método tradicional (ou seja até o Java 7), faríamos o uso de um “for” qualquer para iterar sobre a lista, logo se precisássemos imprimir os nomes dos titulares das contas no exemplo dado ficaria parecido com o exemplo abaixo:

for (Conta c : listaDeContas) {
    System.out.println(c.getNome());
}

Utilizando os novos recursos do stream e as expressões lambda, esta mesma operação pode ser simplificada para o trecho de código a seguir:

listaDeContas.forEach(conta -> System.out.println(conta.getNome()));

Observamos como fica menos verboso e legível o código, este é um dos objetivos desta nova interface, facilitar a vida do programador tanto na escrita do código quanto na leitura.

2. ORDENAÇÃO

Como podemos fazer uso da nova interface Stream para ordenar uma coleção?
No Java 7 se você fizer o uso de um “comparator” para ordenar em ordem crescente pelo número da conta presente em sua lista de objetos a implementação de seu código seria algo parecido com o trecho logo a seguir:

Collections.sort(listaDeContas, new Comparator<Conta>() {
     public int compare(Conta conta1, Conta conta2) {
          if(conta1.getNumeroDaConta() < conta2.getNumeroDaConta()) {
               return -1;
          }

          if (conta1.getNumeroDaConta() > conta2.getNumeroDaConta()) {
               return 1;
          }
          return 0;
     }
});
listaDeContas.forEach(conta -> System.out.println(conta.getNumeroDaConta()));

Já com a utilização do stream e com a expressão lambda podemos simplificar o código conforme o trecho a seguir:

listaDeContas.stream()
     .sorted((c1, c2) -> c2.getTitular().compareTo(c1.getTitular()))
     .forEach(lista -> System.out.println(lista.getNumeroDaConta()));

O primeiro passo é converter nossa lista de contas em uma interface do tipo stream, para isto basta invocar o método stream() (IMPORTANTE: vale a pena ressaltar que neste momento temos um objeto do tipo stream, não estamos alterando a lista mas sim o objeto criado), agora que temos uma interface stream, basta invocarmos o método sorted() que retorna os elementos ordenados conforme o comparador e logo após fazemos o uso do forEach() que faz uma iteração sobre os elementos da lista e a imprime.

3. BUSCA

Digamos que tenhamos que buscar em uma lista somente os titulares que se iniciam com a letra “J”, até o Java 7 faríamos algo como:

for (Conta conta : listaDeContas) {
     if(conta.getTitular().startsWith("J")){
          System.out.println(conta.getTitular());
     }
}

Já com a utilização do stream e com a expressão lambda podemos simplificar o código conforme o trecho a seguir:

listaDeContas.stream()
     .filter(conta -> conta.getTitular().startsWith("J"))
     .forEach(conta -> System.out.println(conta.getTitular()));

Aqui utilizamos um método que você vai usar muito quando se acostumar a fazer uso do stream o método filter() que recebe um parâmetro do tipo “Predicate” e faz uso de expressões lambdas para aplicar os filtros, e novamente utilizamos o forEach() para iterar sobre a lista e imprimi-la.

4. SOMA

Para somar sobre uma lista podemos fazer também o uso do stream, o resultado fica bem parecido com o trecho de código anterior. Digamos que desejamos somar os respectivos valores das contas dos titulares que se iniciam com a letra “J”, o código ficaria assim:

Double totalEncontrado = listaDeContas.stream()
     .filter(conta -> conta.getTitular().startsWith("J"))
     .mapToDouble(conta ->; conta.getValor())
     .sum();
System.out.println(totalEncontrado);

Para esta tarefa as novidades aqui são os métodos mapToDouble() que devolve um DoubleStream consistindo dos resultados dos filtros obtidos no método filter() e também temos o método sum() que é responsável por realizar a soma.

Bom aqui estão algumas operações básicas com a utilização do stream, espero ter colaborado para facilitar seu trabalho no dia dia, deixando seu código mais enxuto e legível.

Para você que tem o interesse de saber um pouco mais sobre o stream é só clicar aqui  para ver sua documentação.

Não esqueça de deixar um comentário com a sua opinião, até a próxima e que a força esteja com você… \o/