Atingimos 100% de cobertura de testes! Será mesmo?

Samuel Lucas
CWI Software
Published in
5 min readNov 27, 2019

Coautor: Ernesto Barbosa 🚀

Quando falamos de qualidade de software, principalmente com ênfase em código, boa parte de nossa garantia provém do quanto cada parte é coberta por testes — sejam eles unitários, de integração ou de outras camadas. Porém, devido ao aumento de complexidade, regras e código — tanto de aplicação, quanto de testes — conforme o projeto aumenta, precisamos de ferramentas auxiliares para literalmente forçar erros no código enquanto executamos nossos testes, para assim, sabermos se estes estão capturando as falhas (conforme o esperado) ou deixando elas passarem — livres, leves e soltas.

A esta prática, damos o nome de testes de mutação.

Cobertura de código

Como funciona o processo de avaliação de cobertura de código (grande parte)

A métrica de cobertura de código avalia, considerando o número total de linhas de código do projeto, qual o quociente de total de linhas / total de linhas cobertas, ou em outras palavras, quanto do código é testado. Para obter esta métrica, as ferramentas de cobertura — grande maioria — executam os testes do projeto e, para cada teste executado, contabilizam os trechos de código invocados. Ao final da execução, é feito o cálculo e algumas delas geram relatórios ilustrando quais partes do código não foram chamadas.

Como funciona o processo de avaliação de cobertura de código mutante

Semelhante ao processo de cobertura de código comum, esta métrica avalia o quanto de código é testado. Entretanto, neste caso adicionamos uma variável a mais: mutações no código. Podemos organizar essas “mutações” em diferentes categorias, sendo: mutação de declarações de métodos, mutação de condições e mutação de valores.

Obs.: Vale lembrar que para linguagens de programação, existem ainda ferramentas que realizam mutações em diferentes níveis, como: class-level, considerando regras específicas de orientação a objetos (encapsulamento, herança, polimorfismo) e method-level, seguindo as três categorias citadas acima.

Para obter esta métrica, de forma lúdica, adotamos a nomenclatura de mutantes para os trechos de código que são modificados. Quanto maior a quantidade de mutantes mortos — ou seja, identificados pelos testes — mais eficiente é o teste e, consequentemente mais confiável é sua cobertura. Em contraponto, para cada mutante que sobrevive — ou seja, um trecho de código coberto por testes que passam mesmo havendo erros — é necessário uma avaliação e, possivelmente, uma refatoração dos testes relacionados até que estes passem a identificar (e matar, claro) os mutantes.

Ferramentas

A execução de testes de mutação é feita de forma automatizada. Para isso, utilizamos ferramentas que podem ser integradas ao projeto, realizar os testes e gerar os relatórios com o resultado das mutações / testes:

Mão na massa! (Exemplo javascript)

Para o nosso exemplo, vamos demonstrar um passo a passo para implementação em uma aplicação javascript, que já possui alguns testes unitários, utilizando a biblioteca Stryker.

  1. Faça download deste projeto (zip ou git clone): https://github.com/samlucax/mutation-testing-with-stryker-javascript
  2. Instale as dependências: npm install
  3. Instale o stryker-cli: npm install -g stryker-cli
  4. Configure o stryker no projeto: stryker init

Responda as perguntas que serão exibidas conforme abaixo:

? Are you using one of these frameworks? Then select a preset configuration. None/other

? Which test runner do you want to use? If your test runner isn’t listed here, you can choose “command” (it uses your `npm test` command, but will come with a big performance penalty) karma

? Which test framework do you want to use? jasmine

? What kind of code do you want to mutate? javascript

? [optional] What kind transformations should be applied to your code?

? Which reporter(s) do you want to use? html, clear-text, progress

? Which package manager do you want to use? npm

IMPORTANTE: Lembre-se que estas respostas servem para as tecnologias usadas no projeto de exemplo. Ao configurar para seu projeto, confira a documentação e responda de acordo com as suas tecnologias e preferências.

5 . Após concluir a configuração, invoque os mutantes: stryker run

6. Se tudo funcionou corretamente, você pode conferir um relatório html da execução na pasta reports/mutation.

Exemplo de resultado do relatório, no formato html.
Resultado da execução dos testes de mutação

7. Se você quiser comparar o resultado dos testes de mutação com a cobertura de testes do projeto, execute npm run test. Após isso, será gerado um relatório da cobertura de testes do projeto na pasta reports/coverage.

Relatório com a cobertura de testes do projeto.
Relatório com a cobertura de testes do projeto: 100%

Perceba que mesmo neste pequeno exemplo, para um projeto que possui 100% de cobertura de código, muitas falhas passam despercebidas: 47 mutações não foram identificadas pelos testes. 😥

Conclusão

Cuide de seus testes, e eles cuidarão de seu código.

As mutações são um aviso sobre nossos testes. Por isso, analise o resultado dos testes de mutações e aplique os ajustes necessários, seja no código de aplicação ou no de testes. O importante é ter garantias de que, se algo falhar — por mutante ou erro humano — o teste relacionado também vai falhar, e assim será possível realizar o ajuste.

Q & A:

Algumas perguntas feitas sobre o tema. Contribua!

Porque as ferramentas que medem cobertura de código, já não incluem testes de mutação (ou a capacidade de) para avaliar de fato se o código está “livre” de erros?

Em que momento do projeto então eu deveria incluir (ou executar) meus testes de mutação?

Devo executar os testes de mutação sempre para todo o projeto ou posso executar apenas para um contexto / feature específica?

Qual o impacto na cobertura de testes, quando ajustamos para matar os mutantes não identificados pelos testes? 300% de cobertura?

Testes de mutação servem apenas para testes unitários ou servem também para outras camadas de testes? Como integração, funcionais e de ponta a ponta?

É possível criar mutações customizadas ou somente as padrões?

Então quer dizer que estou fazendo Teste do Teste?

De forma geral, sim. É um paradoxo que parece um pouco estranho, eu sei. Mas se concordamos com o fato de que o código fonte deve ser testado, logo os testes unitários — que são parte do código fonte — também devem ser. Entretanto, testar todas as variações da aplicação enquanto executamos os nossos testes unitários é praticamente impossível se feito manualmente. Por isso, ferramentas de testes de mutação podem auxiliar a checar a confiabilidade dos nossos testes, e consequentemente, reduzir a chance de ter erros que passam despercebidos.

Veja / Leia também:

Leia também:

--

--

Samuel Lucas
CWI Software

Test Engineer at CWI Software. Currently working with testing for mobile, api & web.