├── src
└── cnh.js
├── .gitignore
├── __tests__
└── cnh.spec.js
├── stryker.conf.js
├── package.json
└── README.md
/src/cnh.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = podeTirarCarteiraDeMotorista = idade => {
3 | return idade >= 18
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | node_modules/
3 | reports/
4 |
5 | # stryker temp files
6 | .stryker-tmp
7 | stryker.log
8 |
--------------------------------------------------------------------------------
/__tests__/cnh.spec.js:
--------------------------------------------------------------------------------
1 | const podeTirarCarteiraDeMotorista = require('../src/cnh')
2 |
3 | test('Deve retornar false para pessoa com menos de 18 anos', () => {
4 | expect(podeTirarCarteiraDeMotorista(17)).toBe(false)
5 | })
6 |
--------------------------------------------------------------------------------
/stryker.conf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@stryker-mutator/api/core').StrykerOptions}
3 | */
4 | module.exports = {
5 | mutate: [
6 | 'src/cnh.js'
7 | ],
8 | reporters: ['html', 'clear-text'],
9 | testRunner: 'jest',
10 | jest: {
11 | enableFindRelatedTests: false,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "license": "MIT",
3 | "repository": {
4 | "type": "git",
5 | "url": "https://github.com/PauloGoncalvesBH/Testes-de-mutacao-em-JS.git"
6 | },
7 | "dependencies": {
8 | "@stryker-mutator/core": "4.1.2",
9 | "@stryker-mutator/jest-runner": "^4.1.2",
10 | "jest": "^26.6.3"
11 | },
12 | "scripts": {
13 | "test": "jest",
14 | "test:coverage": "jest --collectCoverage true",
15 | "test:mutation": "stryker run"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Teste de mutação 👽: O que é e como fica a cobertura de código?"
3 | published: true
4 | description: "Quem testa os testes? Como podemos garantir a eficiência dos testes? Conheça a resposta para essas perguntas"
5 | tags: testes, ptbr, javascript
6 | canonical_url:
7 | cover_image:
8 | series:
9 | ---
10 |
11 | ## Sumário
12 | - [Who tests the tests?](#who-tests-the-tests)
13 | - [Porque cobertura de código não é confiável](#porque-cobertura-de-código-não-é-confiável)
14 | - [Testes de mutação](#testes-de-mutação)
15 | - [Detalhe da execução](#detalhe-da-execução)
16 | - [RIP Cobertura de código?](#rip-cobertura-de-código)
17 | - [Desvantagem](#desvantagem)
18 | - [Adoção em grandes projetos - Case Google](#adoção-em-grandes-projetos---case-google)
19 | - [Verificando na prática](#verificando-na-prática)
20 | - [Cobertura de código](#cobertura-de-código)
21 | - [Teste de mutação](#teste-de-mutação)
22 | - [Fontes](#fontes)
23 |
24 | ---
25 |
26 | _Esse conteúdo foi apresentado com mais detalhes e profundidade na live do AT Talks em 25/11/20. [Para assistir clique aqui.](https://www.youtube.com/watch?v=TIiVYhoEB8o)_
27 |
28 |
Who tests the tests?
29 |
30 | Quando pensamos em validar a eficiência dos testes implementados, normalmente o que vem à mente é a métrica de cobertura de código. Porém, será que ela realmente é a melhor prática para garantir que os testes estão realmente testando os cenários possíveis?
31 |
32 | > Cobertura de código é uma métrica que valida o quanto do código foi coberto pelos testes.
33 | > Ou seja, verifica quais linhas do código foram executadas ao rodar os testes e retorna o percentual de cobertura.
34 | >
35 | > _Leia esse [conteúdo (en)](https://en.wikipedia.org/wiki/Code_coverage) e [esse (pt-br)](https://medium.com/liferay-engineering-brazil/um-pouco-sobre-cobertura-de-c%C3%B3digo-e-cobertura-de-testes-4fd062e91007) para saber mais._
36 |
37 | Utilizando apenas a métrica de quantidade de cobertura de código não conseguimos garantir que todos os cenários foram cobertos, apenas... quais linhas foram executadas 😮.
38 |
39 | Pense um pouco sobre isso. Alguma vez já viu um teste sem asserção apenas para aumentar a cobertura de código? Tenho certeza que já soube de uma situação parecida.
40 |
41 | Claro que nessa situação, para evitarmos engraçadinhos, basta colocarmos alguma biblioteca que valida que todos os testes possuem asserção e que o percentual de cobertura de código está acima de algum número mágico, como 80%.
42 |
43 | O problema é que, como dito, a cobertura de código não valida a eficiência dos testes, e vamos ver abaixo o porquê.
44 |
45 | ---
46 |
47 | Porque cobertura de código não é confiável
48 |
49 | Abaixo temos um pequeno método que possui apenas 1 teste validando o seu comportamento.
50 |
51 | > _**Disclaimer**_
52 | > É perceptível que esse método possui mais de 1 cenário, porém precisamos de exemplo prático e simples para comparar _cobertura de código_ e _teste de mutação_.
53 |
54 | ```js
55 | // ./src/cnh.js
56 | module.exports = podeTirarCarteiraDeMotorista = idade => {
57 | return idade >= 18
58 | }
59 |
60 | // ./__tests__/cnh.spec.js
61 | test('Deve retornar false para pessoa com menos de 18 anos', () => {
62 | expect(podeTirarCarteiraDeMotorista(17)).toBe(false)
63 | })
64 | ```
65 |
66 | Se verificarmos a cobertura de código do arquivo `cnh.js`, será apresentado que ele foi 100% coberto (aqui o gestor comemora), porém sabemos, por ser um teste simples, que a validação não está eficiente e que poderíamos validar outros cenários, como:
67 | 1. Deve retornar `true` se idade for igual a `18`
68 | 1. Deve retornar `true` se idade for igual a `19`
69 |
70 | #### Cobertura de código de `cnh.js`:
71 |
72 | 
73 |
74 | E é baseado nessa brecha da métrica de linhas executadas é que o uso do teste de mutação faz sentido.
75 |
76 | > "...100% code coverage score only means that all lines were exercised at least once, but it says nothing about tests accuracy or use-cases completeness, and that’s why mutation testing matters"
77 | > _Baeldung, 2018_
78 |
79 | ---
80 |
81 | Testes de mutação
82 |
83 | > Imagine um sanduíche coberto com uma pasta. Cobertura de código vai te dizer que o pão está 80% coberto com pasta. O teste de mutação, por outro lado, vai dizer que a pasta é _chocolate_ e não... bem... qualquer outra coisa.
84 |
85 | O conceito de teste de mutação é bem simples:
86 |
87 | Bugs, ou **mutantes**, são inseridos no código e os testes são executados em cima do código mutado. Se pelo menos 1 dos testes quebrar ou tiver timeout, o mutante é considerado morto 💀 e aquele trecho de código alterado é considerado como coberto pelos testes.
88 |
89 | Ainda não está claro? Então vamos lá.
90 |
91 | Abaixo está o nosso código original:
92 |
93 | ```js
94 | // ./src/cnh.js
95 | const podeTirarCarteiraDeMotorista = idade => {
96 | return idade >= 18
97 | }
98 | ```
99 |
100 | O teste de mutação irá detectar todos os pontos que podem ser alterados no código e atuar em cima deles. No nosso exemplo serão feitas as seguintes alterações (serão 5 mutantes no total):
101 | - A expressão condicional `idade >= 18` será alterada para `true` e `false`;
102 | - O operador de idade `>=` será alterado para `<` e `>`;
103 | - O bloco `=> { return idade >= 18 }` será alterado para `=> {}`.
104 |
105 | > Quer entender tudo que será mutado no seu código e para o que será mutado? Leia '[Mutantes suportados pelo stryker (en)](https://github.com/stryker-mutator/stryker-handbook/blob/master/mutator-types.md)'.
106 |
107 | A cada alteração feita, todos os testes criados são executados. Se algum teste quebrar, significa que aquela alteração (**mutação**) está coberta, então ela foi assassinada.
108 |
109 | É um pouco confuso a questão de que para que aquela mutação seja considerada como morta (sucesso) é preciso que algum teste quebre (afinal, teste quebrar é ruim). Porém temos que entender que o nosso teste foi feito para o cenário ABC e se o cenário foi alterado para ABZ, o nosso teste tem que detectar essa mudança e falhar.
110 |
111 | > O teste de mutação é nada mais e nada menos do que automatizar todo o processo de "sabotar o código e executar testes para ver se eles falham"
112 |
113 | Se executarmos teste de mutação utilizando o teste e código apresentados anteriormente, o resultado seria esse:
114 |
115 | 
116 |
117 | Tínhamos 100% de cobertura de código, porém o teste de mutação revelou que 2 mutações criadas não resultaram em quebra do nosso teste (sobreviventes), demonstrando que há brecha no nosso teste.
118 |
119 | Para que todos os 5 mutantes não sobrevivam, precisamos criar um novo teste que cubra essa brecha, como:
120 |
121 | ```js
122 | test('Deve retornar true para pessoa maior de 18 anos', () => {
123 | expect(podeTirarCarteiraDeMotorista(18)).toBe(true)
124 | })
125 | ```
126 |
127 | Detalhe da execução
128 |
129 | Quando executamos o teste de mutação são feitas as seguintes etapas:
130 | 1. Analisa quais arquivos serão mutados;
131 | - No nosso caso foi `cnh.js`.
132 | 1. Executa todos os testes e espera que todos passem;
133 | - O teste é abortado se algum teste falhar. Para validar se algum teste quebrou com mutação é imprescindível que todos os testes sejam executados com sucesso com o código original.
134 | 1. Gera mutante para todos os trechos de código;
135 | - No nosso caso foram 5 mutantes criados.
136 | 1. Executa todos os testes para cada mutante gerado;
137 | 1. A pontuação final do teste é de acordo com a quantidade de mutantes que foram mortos ou resultaram em timeout em comparação com a quantidade total de mutantes.
138 |
139 |
140 | RIP Cobertura de código?
141 |
142 | Embora teste de mutação seja uma métrica muito interessante para entendermos a saúde dos testes criados, é importante salientar que ele **NÃO** substitui a cobertura de código, atuando apenas como complemento e possui algumas desvantagens que impedem fortemente a sua adoção em larga escala.
143 |
144 | Portanto, cobertura de código continuará sendo uma métrica bastante usada e não é uma ferramenta antagonista ao teste de mutação
145 |
146 | Desvantagem
147 |
148 | Como o teste de mutação analisa todos os possíveis pontos que podem ser mutados no código e executa todos os testes para cada mutação, ele possui uma execução que onera bastante a máquina e possui um alto tempo de execução.
149 |
150 | Devido à necessidade de ter um alto poder computacional, o uso do teste de mutação chega a ser proibitivo em projetos médios e grandes.
151 |
152 | Um exemplo dessa limitação é o projeto [ServeRest](https://github.com/PauloGoncalvesBH/serverest). Todos os 86 testes existentes são executados em aproximadamente 550 milissegundos, enquanto os testes de mutação atuam em cima de 22 arquivos, resultando em 599 mutantes e com execução média de 19 minutos.
153 |
154 | > No nosso código de exemplo a cobertura de código é executada em 9 ms enquanto o teste de mutação é executado em 3 segundos.
155 |
156 | Adoção em grandes projetos - Case Google
157 |
158 | >> **A seção _Adoção em grandes projetos - Case Google_ será atualizado com os detalhes das estratégias de testes de mutação em breve. Esses detalhes foram apresentados na [live do AT Talks](https://www.youtube.com/watch?v=TIiVYhoEB8o).**
159 |
160 | Essa limitação de poder computacional não impediu a adoção do teste de mutação pela Google nos seus códigos ([que possuía 2 bilhões de linhas em 2018](https://dl.acm.org/doi/pdf/10.1145/2854146)), porém ela teve que utilizar de algumas estratégias de criação da mutação.
161 |
162 | > Traditional mutation analysis is computationally prohibitive which hinders its adoption as an industry standard. In order to alleviate the computational issues, we present a diff-based probabilistic approach to mutation analysis that drastically reduces the number of mutants by omitting lines of code without statement coverage and lines that are determined to be uninteresting - we dub these arid lines.
163 | > _State of Mutation Testing at Google_
164 |
165 | No bom português:
166 |
167 | > A análise de mutação tradicional é computacionalmente proibitiva, o que impede sua adoção como um padrão da indústria. A fim de aliviar os problemas computacionais, apresentamos uma abordagem probabilística baseada em diff para análise de mutação que reduz drasticamente o número de mutantes, omitindo linhas de código sem cobertura de instrução e linhas que são determinadas como desinteressantes - dublamos essas linhas áridas.
168 | > _Estado do teste de mutação na Google_
169 |
170 | Para entender a fundo a estratégia adotada por essa companhia, leia a publicação de pesquisa sobre o [estado do teste de mutação na Google](https://research.google/pubs/pub46584/), feita para a ocasião da _40ª Conferência Internacional de Engenharia de Software_.
171 |
172 | ---
173 |
174 | Verificando na prática
175 |
176 | Para executar a cobertura de código e teste de mutação citados nesse texto, primeiramente clone [esse repositório](https://github.com/PauloGoncalvesBH/teste-de-mutacao), executando:
177 |
178 | ```sh
179 | git clone https://github.com/PauloGoncalvesBH/teste-de-mutacao.git
180 | ```
181 |
182 | Instale as dependências com o comando `npm install`.
183 |
184 | Testes
185 |
186 | O teste foi implementado utilizando [jest](https://www.npmjs.com/package/jest). Para rodar os testes execute:
187 |
188 | ```sh
189 | npm test
190 | ```
191 |
192 | Cobertura de código
193 |
194 | Para rodar a cobertura de código, execute:
195 |
196 | ```sh
197 | npm run test:coverage
198 | ```
199 |
200 | Teste de mutação
201 |
202 | O teste de mutação é executado com a biblioteca [stryker](https://www.npmjs.com/package/@stryker-mutator/core) e com o runner do [stryker para jest](https://www.npmjs.com/package/@stryker-mutator/jest-runner). Para rodar o teste de mutação execute:
203 |
204 | ```sh
205 | npm run test:mutation
206 | ```
207 |
208 | #### Desafio
209 |
210 | O que acha de aumentar o score do teste de mutação de _60%_ para _100%_?
211 |
212 | Crie novo teste no arquivo [cnh.spec.js](/__tests__/cnh.spec.js) que mate 👿 as 2 mutações que estão sobrevivendo e mantenha a cobertura de código em _100%_.
213 |
214 | ---
215 |
216 | Fontes
217 |
218 | Os seguintes materiais forneceram conteúdo e base para a criação desse texto:
219 |
220 | - [State of Mutation Testing at Google](https://research.google/pubs/pub46584/)
221 | - [Mutation Testing - Wikipedia](https://en.wikipedia.org/wiki/Mutation_testing)
222 | - [Apresentação 'An intro to mutation testing - or why coverage sucks'](https://speakerdeck.com/pedrorijo91/mutation-testing-pixels-camp-2019)
223 | - [Mutantes suportados pelo Stryker](https://github.com/stryker-mutator/stryker-handbook/blob/master/mutator-types.md)
224 | - [Mutation Testing: What It Is and How It Makes Code Coverage Matter](https://dev.to/carlosschults/mutation-testing-what-it-is-and-how-it-makes-code-coverage-matter-ijp)
225 | - [Code coverage is useless](https://dev.to/johnpreese/code-coverage-is-useless-1h3h)
226 | - [Why code coverage is not a reliable metric](https://dev.to/conectionist/why-code-coverage-is-not-a-reliable-metric-327l)
227 | - [Mutation testing in 1000 characters](https://dev.to/schreiber_chris/mutation-testing-in-1000-characters-193a)
228 | - [Why Google Stores Billions of Lines of Code in a Single Repository](https://dl.acm.org/doi/pdf/10.1145/2854146)
229 |
230 | ---
231 |
232 | ##### _Esse post está sendo versionado e hospedado no [Github](https://github.com/PauloGoncalvesBH/teste-de-mutacao)_
233 |
--------------------------------------------------------------------------------