Mutation Testing: Melhorando a Qualidade dos Testes

Introdução a Qualidade de Software · 6 de julho de 2026

📖 6 min de leitura

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

  1. Generate Mutations: Criar variantes do código
  2. Run Tests: Executar suite contra cada mutante
  3. Classify Results: Identificar se mutante foi “matado”
  4. 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)
    1. 80-90{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}: Bom

    2. 60-80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}: Precisa melhorar

    3. <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 ===============

  1. src/calculator.py:32

- Changed > to >=
- Still passes: test_desconto_100_porcento
  1. src/validator.py:45

- Changed && to ||
- Still passes: test_validate_multiple_conditions

=============== RECOMMENDATIONS ===============

  1. Adicione teste para valor exatamente 18

  2. 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:

  1. Entender conceito – Muta código, verifica se testes detectam
  2. Usar ferramentas – mutmut, PITest, Stryker
  3. Interpretar resultados – Score alto = testes fortes
  4. Criar testes para mutantes sobrevientes – Melhoria contínua
  5. 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.