O que a quantidade de asserts em um teste nos diz sobre o código?

Todo teste pode ser separado em três: a parte onde você monta o cenário, a parte onde você executa a ação que quer testar, e a parte que você valida que a ação aconteceu da maneira esperada. Por exemplo, veja o código abaixo:

@Test
public void deveCalcularNF() {
  // 1o: cenário
  NotaFiscal nf = new NotaFiscal("Mauricio", 2000.0);

  // 2o: ação
  double imposto = nf.calculaImposto();

  // 3o: validação
  assertEquals(250.0, imposto, 0.00001);
}

A quantidade de “asserts” pode ser ilimitada. Podemos ter 1 (como no código acima), 2, 3, ou quantos mais precisarmos. Entretanto, quanto mais código em um método de teste, pior sua legibilidade. Portanto, é fácil perceber que, quanto mais asserts em um teste, mais difícil é lê-lo e entendê-lo.

Mas uma pergunta cuja resposta pode ser interessante é: será que o desenvolvedor foi “forçado” a escrever muitos asserts porque o código de produção que está sendo testado (em nosso caso, o calculaImposto()) não está bem feito?

Após executarmos um experimento controlado em alguns projetos de código aberto da Apache, concluímos que: não, a baixa qualidade do código de produção não implica em um maior número de asserts por teste.

Mas, durante o experimento, percebemos que contar a quantidade de asserts pode não ser uma boa ideia. Às vezes o desenvolvedor faz uso de mais de um assert porque o objeto foi modificado em mais de um atributo. Por exemplo:

assertEquals(200.0, nf.getImpostoA(), 0.0001);
assertEquals(250.0, nf.getImpostoB(), 0.0001);

Um outro desenvolvedor poderia ter implementado o teste, usando um assertEquals() apenas, e comparando o objeto inteiro:

assertEquals(nfEsperada, nf);

Ou seja, aqui é mais ou menos questão de gosto do desenvolvedor. Nesse momento, levantamos uma outra hipótese: talvez contar a quantidade de asserts não faça sentido, mas sim contar a quantidade de objetos diferentes que recebem asserts em um mesmo teste. Por exemplo, veja o código abaixo, onde temos 3 asserts, mas em 2 objetos diferentes:

assertEquals(100.0, nf1.getImpostoA());
assertEquals(100.0, nf1.getImpostoB());
assertEquals(250.0, nf2.getImpostoA());

Se verificarmos a relação entre a “quantidade de objetos que receberam asserts em um teste” e “qualidade do código de produção”, descobrimos que: sim, um código de produção problemático pode implicar em mais de um objeto recebendo assert no teste!

Isso é interessante! Quando o código de produção é muito complicado, os desenvolvedores muitas vezes preferem escrever menos testes, mais complexos, e testar diferentes entradas. Isso serve como um ótimo feedback para você desenvolvedor: se você está fazendo asserções em mais de um objeto, olhe seu código de produção; ele pode ter problemas!

Este trabalho foi apresentado no CSMR 2013, em Gênova, e é uma pequena parte da minha pesquisa de doutorado. Muito do que aprendi está no meu livro sobre TDD e nos cursos de testes automatizados online.

17 Comentários

  1. Rafael Brizola 27/08/2013 at 10:50 #

    Pessoalmente eu prefiro um assert por test. Acho que fica muito mais legível e mais fácil de manter.

    Esse tópico do stackoverflow tem uma discussão interessante sobre isso.

    http://stackoverflow.com/questions/6252187/junit-one-assert-per-test-method-or-multiple-asserts-per-test-method

  2. Mauricio Aniche 27/08/2013 at 13:19 #

    É um assunto realmente bem polêmico, Rafael. Claro, código mais simples é sempre melhor. Mas o estudo que descrevi acima me fez ser menos “chato” com essa regra do que era antes.

    Faz sentido?

    Um abraço!

  3. Eduardo Guerra 27/08/2013 at 15:55 #

    Uma outra questão que seria interessante medir são quantas verificações um teste possui. Nesse caso, uma forma de contar verificações seria contar a quantidade de grupos de asserts separados por ações. Nesse caso, um número grande de verificações poderia indicar um método de teste que contém vários casos de teste.

    Um outro caso problemático seria ter conjuntos de asserts recorrentes em diversos testes. Nesse caso, isso indica que ou essa lógica de comparação deveria estar na classe ou seria adequada a criação de um método de asserção que encapsulasse aquela lógica.

    Uma coisa a se pensar é que talvez o número de asserções não diga muita coisa, mas combinado com outras informações pode revelar questões interessantes sobre o código.

  4. Mauricio Aniche 27/08/2013 at 16:16 #

    Oi Eduardo,

    É verdade. Quanto mais informação, melhor. Mas combinar métricas é ainda algo difícil e complicado.

    Vc deu bons insights. Quem sabe um próximo estudo!?

    Um abraço!

  5. Luiz Fernando Oliveira Corte Real 27/08/2013 at 18:10 #

    Resultados muito interessantes!

    Acho que discordo de uma afirmação sua (apesar de achá-la válida no caso geral): “Portanto, é fácil perceber que, quanto mais asserts em um teste, mais difícil é lê-lo e entendê-lo.”. Pode ser que um assert seja tão complicado de ler que seja melhor ter 2, não? Por exemplo:

    assertTrue(pessoa != null && pessoa.getPai() != null && pessoa.getPai().getPai() != null);

    Versus:

    assertNotNull(pessoa);
    assertNotNull(pessoa.getPai());
    assertNotNull(pessoa.getPai().getPai());

    Acho que isso pode ter relação com o primeiro resultado que você encontrou. Nesse caso também faz sentido ter mais de um assert.

  6. Robson Castilho 27/08/2013 at 23:53 #

    Olá,
    Há mais de 1 ano desencanei de regra “1 assert por teste” cega.

    A maioria dos testes ainda continua com um assert só, mas em outros casos acho válido (desde que contra o mesmo objeto como citado no texto).

    Também não vejo pecado algum em um mesmo teste, onde estou testando o Equals, eu usar 2 asserts: uma para verificar que o objeto é igual a outro e no segundo, para verificar que ele é diferente de um terceiro. Durmo bem com isso.

    Só tomo o cuidado para não testar 2 objetos diferentes no mesmo teste e também não faço a asserção contra 2 mocks diferentes (1 mock por teste).

    []s

  7. Thiago Senna 29/08/2013 at 09:51 #

    Eu costumo utilizar assert também para chegar se o código está no estado correto/pronto para ser testado. Isso pq outro programador pode alterar o teste e quebrar as condições minimas para a execução do teste.

  8. Thiago Senna 29/08/2013 at 09:55 #

    Aliás, não concordo muito com a comparação entre legibilidade do código com o número de asserts. Isso não faz sentido. Escrever código legível está mais relacionado ao quanto o programador gosta de escrever código legível do que ao número de asserts. É perfeitamente possível no final do código ter apenas um assert enquanto as 30 linhas anteriores que antecedem ao teste não ser legível.

  9. Alberto Souza 03/09/2013 at 23:25 #

    Thiago Senna, que é possível é. Mas no estudo dele ele chegou a resultados que demonstraram que pode existir uma relação. De um lado tem a sua sensação e do outro tem um estudo cientifico.

  10. Rasa Lariguet 04/09/2013 at 08:48 #

    Na minha visão esse estudo faz todo o sentido. Vamos fazer uma analogia entre um “assertEquals” e um “if”, uma vez que eles se comportam de maneira similar, ambos testam condições e tomam ações sobre o resultado da condição testada. Assim como um código cheio de “ifs” muito provavelmente esconde uma modelagem ruim, um teste com muitos asserts caminha no mesmo sentido.

  11. Mauricio Aniche 04/09/2013 at 12:18 #

    Oi Thiago,

    Sem dúvida. Tudo é possível. Mas talvez essa seja a graça de fazer uso de métodos estatísticos, certo? Ele sabe te dizer se algo é mera coincidência, ou aparentemente não.

    Na amostra que observei, a relação parece que faz algum sentido. No artigo eu discuto razões para isso. Você deu uma olhada?

    Um abraço,

  12. Abraão Isvi 10/09/2013 at 18:53 #

    Muito bom artigo.

  13. Felipe Meyer 11/09/2013 at 11:41 #

    Penso que se uma ação em um sistema deve ser validada em todos os pontos de mudança que aquela ação impactou. (pontos de teste), sejam teste funcionais ou de unidade.

    Ex.: incluir uma nota fiscal no sistema gerou alteração em 5 tabelas diferentes.
    acho que o básico seria validar o que foi mudado nesses 5 pontos.

    Esse fato facilita a vida de toda equipe, na busca pela solução do problema.

  14. Daniel 11/09/2013 at 11:46 #

    Eu trabalho atualmente com testes automatizados, e em muito dos testes a pré-condição é muito extensa. Pra não ter que criar essa pré-condição na mão, eu automatizo a pré-condição também, isso seria errado?! E no caso dos automatizados eu faço vários asserts em um mesmo caso de teste, faço um para verificar se está na página correta, toda vez que mudo de página por exemplo.

  15. Denis Santos 11/09/2013 at 12:00 #

    Concordo contigo, um teste de unidade trata um teste específico, mesmo que o método a ser testado faça duas operações, exemplo:
    Um método que acesse faça uma chamada ao DAO e depois faça uma operação de cálculo com o Objeto retornado pelo DAO. O correto seria ter dois testes de unidade, sendo um para a chamada do DAO, e o outro o cálculo. Se fizermos um único teste de unidade com dois asserts a semântica do teste passa a ficar um pouco complexa, não sei se o exemplo ficou bom, mas é isso, hehe

  16. Alex Rios 11/09/2013 at 14:26 #

    “Apenas um assert por teste” é uma afirmação que vejo sendo constante confundida.

    Acredito que o problema não está na quantidade de comandos de assert dentro do seu teste, mas sim respeitar a anatomia do seu teste tendo apenas 1 bloco de assert, por exemplo:
    Anatomia respeitada
    – Cenário
    – Execução
    – Teste

    Não respeitada
    – Cenário
    – Execução
    -Teste
    – Execução
    -Teste

    A regra de ouro é que todo o bloco de validação faça quantos asserts forem necessários, desde que validem a mesma coisa, como no caso exmeplo da nota fiscal.

  17. Gustavo Coelho 11/09/2013 at 15:07 #

    @Daniel,

    Concordo com você. O setup do teste pode ser não trivial e eu também procuro automatizar isto. Nestes casos, utilizo asserts para garantir que está tudo ok antes de executar o teste propriamente. No final tem o assert para o teste propriamente.

Deixe uma resposta