Você tem testes para todas as ocasiões? Ou talvez o seu repositório de projetos contenha a ajuda "Cerca de 100% de cobertura de teste"? Mas é tudo tão simples e alcançável na vida real?

Com os testes de unidade, tudo é mais ou menos claro: eles devem ser escritos. Às vezes, eles não funcionam como esperado: há falsos positivos ou testes com erros que retornarão sim e não sem nenhuma alteração no código. Pequenos bugs que você pode encontrar em testes de unidade são valiosos, mas geralmente são corrigidos pelo próprio desenvolvedor - antes que ele se comprometa. Mas estamos realmente preocupados com os erros que muitas vezes desaparecem de vista. E o pior de tudo, eles geralmente decidem fazer um nome para si mesmos quando o produto cai nas mãos do usuário.
É um teste de mutaçãopermite que você lide com esses bugs insidiosos. Ele modifica o código-fonte de uma forma predeterminada (introduzindo bugs especiais - os chamados "mutantes") e verifica se esses mutantes sobrevivem em outros testes. Qualquer mutante que sobreviveu ao teste de unidade leva à conclusão de que os testes padrão não encontraram o trecho de código modificado correspondente que contém o erro.
Em Python, mutmut é a principal ferramenta para teste de mutação .
Imagine que precisamos escrever algum código que calcule o ângulo entre os ponteiros das horas e dos minutos em um relógio analógico:
def hours_hand(hour, minutes):
base = (hour % 12 ) * (360 // 12)
correction = int((minutes / 60) * (360 // 12))
return base + correction
def minutes_hand(hour, minutes):
return minutes * (360 // 60)
def between(hour, minutes):
return abs(hours_hand(hour, minutes) - minutes_hand(hour, minutes))
Vamos escrever um teste de unidade básico:
import angle
def test_twelve():
assert angle.between(12, 00) == 0
Não há ifs no código . Vamos verificar até que ponto esse teste de unidade cobre todas as situações possíveis:
$ coverage run `which pytest`
============================= test session starts ==============================
platform linux -- Python 3.8.3, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: /home/moshez/src/mut-mut-test
collected 1 item
tests/test_angle.py . [100%]
============================== 1 passed in 0.01s ===============================
Excelente! Como 100% de cobertura. Mas o que acontece quando fazemos o teste de mutação?

Ah não! Dos 21, até 16 mutantes sobreviveram. Como assim?
Para cada teste de mutação, uma parte do código-fonte precisa ser modificada para simular um erro potencial. Um exemplo de tal modificação é alterar o operador de comparação ">" para "> =". Se não houver teste de unidade para esta condição de limite, este bug mutante sobreviverá: este é um bug potencial que nenhum dos testes usuais irá detectar.
OK. Tudo limpo. Precisamos escrever melhores testes de unidade. Então, usando o comando de resultados, vamos ver quais mudanças específicas foram feitas:
$ mutmut results
<snip>
Survived :( (16)
---- angle.py (16) ----
4-7, 9-14, 16-21
$ mutmut apply 4
$ git diff
diff --git a/angle.py b/angle.py
index b5dca41..3939353 100644
--- a/angle.py
+++ b/angle.py
@@ -1,6 +1,6 @@
def hours_hand(hour, minutes):
hour = hour % 12
- base = hour * (360 // 12)
+ base = hour / (360 // 12)
correction = int((minutes / 60) * (360 // 12))
return base + correction
Este é um exemplo típico de como funciona o mumut: ele analisa o código-fonte e substitui alguns operadores por outros: por exemplo, adição por subtração ou, como neste caso, multiplicação por divisão. Os testes de unidade, em geral, devem detectar erros ao alterar as instruções; caso contrário, eles não testam efetivamente o comportamento do programa. Esta é a lógica que mutmut segue ao fazer certas alterações.
Podemos usar o comando mutmut apply no mutante sobrevivente. Uau, descobrimos que não verificamos se o parâmetro de hora foi usado corretamente. Vamos consertar isso:
$ git diff
diff --git a/tests/test_angle.py b/tests/test_angle.py
index f51d43a..1a2e4df 100644
--- a/tests/test_angle.py
+++ b/tests/test_angle.py
@@ -2,3 +2,6 @@ import angle
def test_twelve():
assert angle.between(12, 00) == 0
+
+def test_three():
+ assert angle.between(3, 00) == 90
Anteriormente, tínhamos verificado apenas 12. Adicionar uma verificação para o valor 3 salvaria o dia?

Este novo teste conseguiu matar dois mutantes: é melhor do que antes, mas mais trabalho precisa ser feito. Não vou escrever uma solução para cada um dos 14 casos restantes agora, porque a ideia já está clara (você pode matar todos os mutantes sozinho?)
Além de medir a cobertura, o teste de mutação também permite que você avalie a abrangência dos seus testes. Desta forma, você pode melhorar os testes: qualquer um dos mutantes sobreviventes é um erro que o desenvolvedor pode ter cometido, assim como um defeito potencial em seu produto. Então, eu desejo que você mate mais mutantes!
Publicidade
VDSina oferece servidores virtuais para Linux e Windows - escolha um dos SO pré-instalados ou instale a partir da sua imagem.
