O Que é Mutation Testing?
Mutation testing é uma técnica que avalia a qualidade dos testes unitários introduzindo pequenas mudanças (mutações) no código-fonte e verificando se os testes detectam essas mudanças.
Analogia
Código Original: if (age >= 18) → permitido
Mutação 1: if (age > 18) → ainda passa? Bug!
Mutação 2: if (age >= 0) → ainda passa? Bug!
Se seus testes passam mesmo com mutações, eles não estão realmente testando o código.
Por Que Mutation Testing?
| Métrica | Com Coverage Tradicional | Com Mutation Testing |
|---|---|---|
| O que mede | Linhas executadas | Testes que detectam bugs |
| Confiança | Baixa | Alta |
| Bug detection | Não verifica | Verifica |
Como Funciona
- Generate Mutations: Criar variantes do código
- Run Tests: Executar suite contra cada mutante
- Classify Results: Identificar se mutante foi “matado”
- Calculate Score: {6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} de mutantes mortos
Tipos de Mutações
Arithmetic Mutations
# Original
result = a + b
# Mutations
result = a - b # +
result = a * b # +
result = a / b # +
result = a ** b # +
result = a {6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} b # +
Relational Mutations
# Original
if (x > 10)
# Mutations
if (x >= 10) # >
if (x
if (x
if (x == 10) # >
if (x != 10) # >
Logical Mutations
# Original
if (is_valid and has_permission)
# Mutations
if (is_valid or has_permission) # and → or
if (not is_valid and has_permission) # negação
if (is_valid and not has_permission) # negação
Conditional Boundary
# Original
while (count < 100)
# Mutations
while (count <= 100) # < → <=
while (count > 100) #
Ferramentas de Mutation Testing
Para Diferentes Linguagens
| Linguagem | Ferramentas |
|---|---|
| Python | mutmut, Cosmic Ray, mutatest |
| Java | PITest (pitest) |
| JavaScript | Stryker Mutator |
| C# | NinjaTurtles, Pitest.NET |
| Ruby | Mutant |
Implementação Prática
Python com mutmut
# Instalação
pip install mutmut
# Configuração (pyproject.toml ou .mutmut.toml)
[mutmut]
paths_to_mutate = src/
test_command = pytest -x
backup_command = git checkout
# Executar mutation testing
mutmut run
# Ver resultados
mutmut results
# Mostrar mutants sobrevientes
mutmut show survived
# Exemplo: Código a ser testado
# src/calculator.py
def calcular_desconto(valor: float, percentual: float) -> float:
"""Aplica desconto percentual a um valor."""
if percentual 100:
raise ValueError("Percentual não pode exceder 100")
desconto = valor * (percentual / 100)
return valor - desconto
# src/test_calculator.py
import pytest
from calculator import calcular_desconto
def test_desconto_basico():
"""Testa desconto simples de 10{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}"""
assert calcular_desconto(100, 10) == 90.0
def test_desconto_zero():
"""Testa que desconto de 0{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} mantém valor"""
assert calcular_desconto(100, 0) == 100.0
def test_desconto_100_porcento():
"""Testa que desconto de 100{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} retorna 0"""
assert calcular_desconto(100, 100) == 0.0
def test_desconto_negativo_deve_falhar():
"""Desconto negativo deve lançar erro"""
with pytest.raises(ValueError):
calcular_desconto(100, -10)
def test_desconto_acima_100_deve_falhar():
"""Desconto acima de 100{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} deve lançar erro"""
with pytest.raises(ValueError):
calcular_desconto(100, 110)
Java com PITest
org.pitest
pitest-maven
1.15.8
org.pitest
pitest-junit5-plugin
1.15.8
com.example.*
com.example.*Test
# Executar
mvn org.pitest:pitest-maven:mutationCoverage
JavaScript com Stryker
# Instalação
npm install -D @stryker-mutator/core
# Configuração (stryker.conf.js)
module.exports = {
testRunner: 'jest',
mutate: ['src/*/.js'],
reporters: ['html', 'clear-text'],
jest: {
projectType: 'react'
}
}
# Executar
npx stryker run
Interpretando Resultados
Score de Mutação
Mutation Score = (Mutantes Mortos / Total de Mutantes) × 100
Interpretação:
- 90-100{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}: Excelente (muitos bugs detectados)
- 80-90{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}: Bom
- 60-80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}: Precisa melhorar
- <60{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}: Suite fraca
Exemplo de Relatório
=============== MUTATION TESTING ===============
Total Mutants: 25
Killed: 23
Survived: 2
Timeout: 0
Skipped: 0
Score: 92{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}
=============== SURVIVING MUTANTS ===============
- src/calculator.py:32
- Changed > to >=
- Still passes: test_desconto_100_porcento
- src/validator.py:45
- Changed && to ||
- Still passes: test_validate_multiple_conditions
=============== RECOMMENDATIONS ===============
- Adicione teste para valor exatamente 18
- Teste edge case para validação AND/OR
Melhores Práticas
1. Combine com Code Coverage
# Mutation testing complementa coverage
# Coverage 100{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} não significa testes bons
# Mutation testing revela gaps
2. Priorize Módulos Críticos
# Execute mutation testing em módulos críticos
mutmut run --paths src/payment/,src/auth/
3. Corrija Mutantes Sobrevientes
# Mutante sobreviente indica teste fraco
def test_critical_path():
# Este teste não detecta mudança de > para >=
result = processar_idade(18)
assert result == "adulto" # Passa mesmo com >=
# Adicione edge case
def test_critical_path_18_anos():
result = processar_idade(18)
assert result == "adulto"
assert processar_idade(17) != "adulto" # Detecta mudança > para >=
4. Automatize no CI
# GitHub Actions
name: Mutation Testing
on:
schedule:
- cron: '0 2 0' # Semanalmente
jobs:
mutation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run mutation tests
run: |
pip install mutmut
mutmut run --paths-to-mutate src/
mutmut results
- name: Check score
run: |
SCORE=$(mutmut results --only-result)
if [ "$SCORE" -lt 80 ]; then
echo "Mutation score below 80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}: $SCORE"
exit 1
fi
Conclusão
Mutation testing é uma técnica poderosa para avaliar qualidade real dos testes. As chaves são:
- Entender conceito – Muta código, verifica se testes detectam
- Usar ferramentas – mutmut, PITest, Stryker
- Interpretar resultados – Score alto = testes fortes
- Criar testes para mutantes sobrevientes – Melhoria contínua
- Automatizar – Execute regularmente no CI
FAQ
P: Mutation testing substitui coverage?
R: Não. Coverage mostra linhas executadas; mutation mostra se testes detectam bugs.
P: É lento?
R: Sim! Pode ser 10-100x mais lento que testes normais. Use CI/discrete.
P: Qual score é bom?
R: 80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}+ é bom, 90{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}+ é excelente.
P: Vale o esforço?
R: Para código crítico, sim. Para código simples, talvez não.
