Um pequeno estudo sobre asserções em testes

Muitas pessoas já ouviram falar da regra “apenas uma asserção por teste” (only one assertion per test), famosa no post do Dave Astels. A regra, como o próprio nome diz, afirma que o programador nunca deve escrever mais de uma asserção pois a necessidade de mais de uma asserção em um teste poderia indicar que o método está fazendo coisas demais.

Há um mês atrás comecei a escrever um post sobre isso, mas meu exemplo era muito fraco. Corri então para olhar testes de alguns dos últimos projetos que participei, e pasmém: na maioria deles só havia uma asserção!

Ao observar os testes que continham mais de uma asserção, percebi que eles aconteciam nos seguintes casos:

  1. Quando o método retorna uma lista, array ou uma classe responsável por armazenar uma coleção de determinado objeto;
  2. Quando o método retorna um novo objeto;
  3. Quando o método é parte de uma DSL;

Nos testes do tipo (1), as asserções mais comuns são para verificar o tamanho da lista e se o elemento existente nela é o esperado. Além disso, se o teste espera mais de um elemento na lista, o teste faz algum tipo de loop para verificar; Nos testes do tipo (2), o método de teste realiza asserções sobre os atributos do objeto retornado; E, finalmente, nos testes do tipo (3), que geralmente testam a DSL, e nesse caso um teste realmente testa mais de um comportamento ao mesmo tempo, e por isso, verifica mais de um comportamento.

Resolvi portanto perguntar a opinião de outros desenvolvedores sobre a regra. Para isso, enviei a pergunta no twitter (de forma não enviesada, perguntando apenas a opinião e, para os que me responderam de volta, exemplos de código aonde isso fizesse sentido).

Algumas pessoas concordaram com a ideia de apenas uma asserção por teste, afirmando que a regra ajuda a manter o código mais simples e mais fácil de manter. Além disso, quando o teste quebra, é fácil perceber o problema.

Outros já discordaram da regra, e afirmaram que o programador deve tentar escrever sempre o menor número possível de asserções, mas que a regra não precisa ser seguida à risca. Discutindo um pouco melhor esse ponto de vista, muitos deles afirmaram que um teste deve testar apenas uma única funcionalidade, não importando o número de asserções necessárias para tal.

Infelizmente a quantidade de códigos enviada foi muito baixa. Mas, o interessante é que um dos códigos enviados se encaixa em (1). Já o outro código enviado, retirado de um livro de Ruby, não se encaixa em nenhum dos exemplos acima, mas pode-se dizer que aquele teste está na verdade testando duas funcionalidades: string com espaços no começo e no fim e string sem espaços.

A ideia desse post é portanto, fomentar essa discussão. Alguém tem um outro exemplo aonde mais de uma asserção por teste faça sentido, mas que não se encaixa nos casos acima?

Agradecimentos

Obrigado ao @elemarjr, @viniciusquaiato, @FabioVazquez, @pedroreys, @pisketti, @danielsilvarj, @carlosgaldino, @rodbv, @cessor, @alexandregazola, que responderam no Twitter.

19 thoughts on “Um pequeno estudo sobre asserções em testes

  1. Renne Rocha

    Mauricio,

    Eu acho um pouco de radicalismo um teste por asserção, mas talvez por eu ter começado a trabalhar realmente com testes a pouco tempo eu não tenha enxergado como isso pode facilitar tanto minha vida. Vou colocar um trecho de código de um teste que eu fiz (teste de uma view do Django), onde usei várias asserções: http://dpaste.com/hold/332955/

    Eu quero testar se um e-mail foi enviado com os dados do formulário. Se for seguir essa regra, eu deveria repetir o código de chamada da view e escrever um teste para cada assert que eu fiz?

    O que você sugere para melhorar esse tipo de teste? 🙂

    Reply
  2. mauricioaniche Post author

    Oi Renne,

    Seu caso cai no caso (2), onde você quer verificar um objeto inteiro. Não tem jeito, você tem mais de um assert por teste!

    Seu teste está bem claro sim, não há o que melhorar! 🙂

    Reply
  3. Tero Kadenius

    Interesting. Just yesterday I came across this article: http://agile.dzone.com/news/why-you-fail-tdd
    The author suggests that testing too much is one of the main factors in cases where a developer is not successful using TDD.

    Btw, I’m sure this topic would interest quite a few people who don’t speak the language.
    Google translate worked for me but I feel there’s potentially a large audience interested in the subject if it was published also in english. Just a suggestion. 🙂

    PS: I was one of the people who responded to your original question on Twitter.

    Reply
  4. ViniGodoy

    Eu também acho que isso é mais uma diretriz, uma boa prática, do que uma regra.

    Os exemplos 1 e 2, que você citou, são casos clássicos que tem menos sentido semântico estarem em testes separados. Não vou enviar exemplos de testes com mais de uma asserção, porque no meu caso eles se encaixam exatamente nesses fatores.

    Também é muito fácil ver que asserção falhou, ou depura-la. Não vejo por que do preciosismo.

    Só uma coisa, não consegui captar o contexto da sigla “DSL” aqui. Você poderia dar um exemplo do item 3?

    PS: Não te respondi no twitter pq ainda não te seguia.

    Reply
  5. Bruno Taboada

    Pergunta:
    Para os dois primeiros casos, para evitar mais de um assert e manter a coesão com a regra (“apenas uma asserção por teste”), não seria o ideal você encapsular essas verificações?, dentro de uma função, por exemplo.

    Reply
  6. Pedro Matiello

    Aniche, sou mais simpático a testar um conceito por teste, mesmo que isso exija mais de uma asserção. Se não me engano, no Clean Code a recomendação é essa. No GOOS você também vê alguns testes neste estilo.

    Tenho alguma coisa aqui:
    http://code.google.com/p/pistache/source/browse/test/pistache/runner/threaded/ThreadedRunnerSpec.scala

    Alguns testes teriam ficado mais unitários se eu tivesse usado alguma biblioteca de mock. Tem também alguns testes sem nenhuma asserção (para detectar deadlocks).

    Reply
  7. Rafael Felix

    Eu uso normalmente uma asserção por teste, porém tive um problema onde meu teste corrige uma aplicação financeira, eu tenho que verificar se o juros e impostos gerados condizem com os indices utilizados, resultado tem muitos asserts no teste, mais muitos mesmo, por exemplo, nas duas primeiras iterações eu verifico se gerou os valores esperados, e se acumulou corretamente, depois itero um mes em juros, e verifico se acumulou corretamente e gerou os valores corretos, depois itero mais um mes, e mais asserts. como preciso de precisão nos resultados, preciso garantir esses asserts e esses valores.

    Reply
  8. Rafael Ponte

    Oi Mauricio,

    Na minha opinião a idéia do “only one assertion per test” serve apenas como um guia, principalmente para quem está começando com testes, evitando-se assim fazer mais asserts do que o necessário e ajudando a identificar problemas no código. Mas isso não necessariamente quer dizer que todo teste tenha que ter um, e somente um assert.

    Como alguns já citaram, vale a pena pensar na feature (funcionalidade) que você está testando, e se para a validação dessa funcionalidade for necessário ter vários asserts então que se faça todos eles, nem que se extraia todos os asserts dentro um único método mais legível.

    Enfim, excelente post.

    Reply
  9. Alexandre Gazola

    Fala Mauricio!

    Legal a discussao! No caso 2, para testar um objeto inteiro, também é comum sobrescrever o metodo equals() e entao o teste ficaria com apenas um assert. Mas as vezes nao vale a pena.

    abracos!

    Reply
  10. mauricioaniche Post author

    @Tero

    Did you reply it? I am almost sure I didn’t receive any english messages but from @cessor ! What’s your twitter login?

    Yes, I may start to translate them to an english version! Hope you keep reading my blog posts until there! 🙂

    @ViniGodoy

    Quando você quer testar uma DSL ou alguma API que faz uso de “fluent interfaces”, por exemplo, às vezes acabo escrevendo testes que encostam em mais de um comportamento. Sei que isso não é o ideal, mas como escrevi poucas APIs realmente fluentes, preciso pensar melhor como resolver esse tipo de problema.

    Veja o seguinte teste de um ORM tosco que estava escrevendo por diversão: https://github.com/mauricioaniche/iceberg/blob/master/Iceberg.Tests/QueryTests.cs

    @Bruno Taboada

    Seria uma ideia sim! Os asserts estariam apenas escondidos, mas eles seriam mais de um, concorda?

    @Pedro Matiello

    Concordo também com essa definição e por isso eu era contra a regra. Mas o meu post foi justamente por isso! Eu não precisei de mais de uma asserção em um teste, a não ser nos casos acima!

    No seu exemplo você faz testes com threads e acaba rodando “mais de uma coisa ao mesmo tempo”, e por isso surge a necessidade de assertar todas as execuções.

    Em um teste mais simples, você tem outro exemplo desses?

    @Rafael Felix

    Tem um pedaço de código pra compartilhar? 🙂

    @Rafael Ponte

    Concordo!

    E obrigado! 🙂

    @Alexandre Gazola

    Boa ideia, Alexandre!

    Mas pq vc acha que “às vezes” não vale a pena? A ideia me pareceu bem boa!

    Reply
    1. Rafael Felix

      Cara,

      não vou por o codigo aqui, por que ele fica gigante, levei mais tempo fazendo o teste que programando a classe, imagine assim, a taxa selic tem uma variação mensal, que faz os indices de uma aplicação financeira variarem durante um mês.
      Eu tenho um for que percorre, 30 ou 31 valores BigDecimais, e chamo o metodo que estou fazendo o teste, que corrige as aplicações por determinados indices.

      no final eu tenho

      assertEquals(new BigDecimal(“23.35”), saldo.getValorIrrf().setScale(2, RoundingMode.HALF_EVEN));
      assertEquals(new BigDecimal(“80.35”), saldo.getValorJuros().setScale(2, RoundingMode.HALF_EVEN));
      assertEquals(new BigDecimal(“1057.00”), saldo.getValorSaldo().setScale(2, RoundingMode.HALF_EVEN));

      isso sem falar nos assert referentes aos valores acumulados no mes, IrrfMes, e JurosMes, e desde o primeiro dita da aplicação IrrfTotal, JurosTotal.

      são em média uns 7 asserts em algumas iterações hhehe.

      Reply
  11. Cezar Guimaraes

    Olá mauricio,

    como alguns comentaram, entendo que isto não é uma regra e sim uma boa prática. como o próprio Dave coloca no sumário dele, ele estava pensando em como tornar o teste simples, expressivo e elegante (” simple, as expressive, and as elegant “).
    além disto, entendo que esta prática é muito boa se você está fazendo TDD. ajuda a ter um teste específico, atingindo um objetivo por vez. como uncle bob tem reforçado bastante ultimamente, quanto mais específico o teste, mais genérico o código (http://cleancoder.posterous.com/the-transformation-priority-premise, http://thecleancoder.blogspot.com/2010/11/craftsman-63-specifics-and-generics.html).
    quando lido com o primeiro caso que você citou, particularmente prefiro quebrar os testes em dois. por exemplo, um que valida o tipo e outro que valida o que retorna da lista. para o segundo caso, na maioria das vezes, sigo o mesmo caminho do alexandre gazola. sobre-escrevo o equals. mas concordo que o valor de cada um dos casos pode (e deve 🙂 ) ser discutido.
    apenas um ponto que é importante ressaltar, principalmente para os que são novos em testes, é que esta regra se referia a testes unitários. para testes de integração, sistema, funcional e outros (como preferirem chamar) fica mais difícil ter um único assert. nestes casos eu tento ter um test por objetivo (escopo) da validação. mas na maioria dos casos isto envolve mais de um assert. eu prefiro seguir esta linha porque acredito que é mais fácil entender o que o teste faz e também a manutenção deles. Testar várias “regras” ou escopos diferentes no mesmo teste cria algumas dificuldades de manutenção. Por exemplo, o teste que o Rafael Felix mencionou, na minha humilde opinião, parece ter muitas validações porque não parece ser um teste unitário. E isto é bom. 🙂 mas talvez seja o motivo de várias validações.
    muito boa a discussão que você levantou.

    []s

    Reply
  12. Julio

    Na verdade existe um simples argumento pra colocar uma asserção por teste – ver qual dos testes falhou. Se você fizer várias asserções em um mesmo teste, terá de verificar em qual linha o teste falhou, o que será custoso durante o desenvolvimento. Acho muito mais pratico ver qual teste falhou, por exemplo.

    Reply
  13. Bruno Taboada

    Concordo sim. Deixa eu tentar me explicar melhor.
    No meu entender “apenas uma asserção por teste”, “por teste”, entendo que são os métodos utilizados para testar. @Test, por exemplo. Portanto, os outros métodos utilizados para esconder os detalhes seriam uns “método auxiliares” para que seja possível atender a regra.

    Abraço!

    Reply
  14. Hugo Corbucci

    Do meu ponto de vista, a ‘regra’ serve para indicar que você gostaria de obter a maior quantidade de informações dos seus testes. Sendo assim, se você encadear asserts um teste, quando o 1o falha, você fica sem saber o resultado dos outros.

    Com RSpec, em Ruby, o bom uso de ‘context’ e ‘describe’ nos permitem montar setup’s curtos para um conjunto pequeno de testes sem precisar mudar de arquivos. Dessa forma, posso garantir que basta eu ler uma vez o setup e entender qual a pré-condição para todo o conjunto de testes. A legibilidade desse teste fica boa porque entendo naturalmente (graças à nossa capicidade de mapeamento visual de indentação) que todos aqueles asserts separados nos próximos testes exigem um determinado estado.
    Sendo assim, fico com o melhor dos 2 mundos: tenho o maior número de informações possíveis quando um assert falha (pq os outros testes são rodados e obtenho seus resultados) e posso ler os testes de forma natural e fluente.

    Em Java, não conheço nenhuma ferramenta que permita isso. Sendo assim, sou forçado a escolher entre:
    1) falhar o meu teste o mais rápido possível e obter menos feedback dos meus testes (usando mais de 1 assert por teste) ou
    2) repetir os contextos para vários testes e garantir 1 assert por testes para ter mais informações.

    Para decidir entre as duas opções, eu me viro para a questão que considero mais importante no desenvolvimento: legibilidade.
    No caso 1), é fácil ser legível e conciso no meu teste. Basta refatorar, extrair métodos e deixar tudo limpo como faço em produção.
    No caso 2), posso extrair métodos mas serei obrigado a repetir suas chamadas em cada um dos métodos que requer aquele setup. Com 1 simples chamada, OK. Mas se você compõe estados (uma forma elegante de montar um estado mais complexo), você acaba ficando com muitas linhas de setup em cada método ou com muitos métodos para abrangir as combinações dos setups que vc precisa. Outra opção é criar um monte de classes de teste para poder aproveitar os setups entre elas.

    Em ambos os casos eu perco em legibilidade. MUITO. Abrir outro arquivo pode ser rápido mas é semelhante a dar um telefonema ao invés de estar do lado ouvindo a conversa (telefone VS comunicação osmótica – Cockburn).

    Mas então, porque considero a legibilidade mais importante do que o feedback? Essencialmente porque você lê muito mais vezes o teste do que os resultados dele te surpreendem. Pense assim, quando um teste falha existem duas situações possíveis:
    1) você sabe que ele ia falhar pq está num ciclo TDD ou mudou a arquitetura numa refatoração.
    2) você não sabe que ele ia falhar e precisa descobrir o que você quebrou.

    No caso 1, você ter mais feedback é inútil. Você já sabia que ia falhar mesmo. Provavelmente você já sabe até o que precisa fazer para passar o teste. O seu benefício de ter mais feedback é próximo de 0.
    No caso 2, quantos casos você já enfrentou em que saber o resultado das outras asserções na primeira execução teria te economizado um bom tempo? No meu caso, pouquíssimos. Na maioria das vezes vou ter que ler o teste, reler o código, simular, tentar entender, algumas vezes até debugar. Sendo assim, mais feedback me ajuda pouco.

    Por outro lado, todas as vezes que eu tiver um caso falhando, eu vou ler o teste. Aliás, provavelmente vou ler os testes algumas vezes mesmo que eles estejam passando para entender como usar meu objeto, qual resultado em determinado caso etc.
    Sendo assim, se eu economizar tempo nessas leituras e gastar um pouco mais quando eles falham sem eu entender porquê, a minha esperança é que eu economize MUITO mais tempo apostando em legibilidade do que em feedback.

    Por isso (desculpem o post nos comentários), na dúvida, dane-se a ‘regra’ de 1 assert por testes. Que viva a regra de ‘testes legíveis’.

    Reply
  15. Alexandre Aquiles

    Nos comentários do artigo, o próprio Dave Astels fala:

    It’s more of something to be thought provoking. I don’t do this all the time in practice, but I do keep it in mind as a worthy goal and aspire to reach it as often as possible/reasonable.

    Há também uma idéia que pode ser interessante: criar os seus próprios assertions.

    Reply
  16. Daniel Freire

    Eu penso que mais uma vez, depende! Acredito que não é um erro inserir mais de uma instrução “assert” por método de teste, desde que elas estejam no contexto do que o método de teste propõe.

    Dos casos que o Aniche menciona acima, os casos 1 e 2 fazem todo sentido pra mim.

    []’s

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *