# Mutation Testing: Melhorando a Qualidade dos Testes

**Meta Description:** Aprenda mutation testing: técnica avançada para avaliar e melhorar a qualidade dos testes unitários. Ferramentas e implementação prática.

---

## 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**: % de mutantes mortos

---

## Tipos de Mutações

### Arithmetic Mutations

```python
# Original
result = a + b

# Mutations
result = a - b    # +
result = a * b    # +
result = a / b    # +
result = a ** b   # +
result = a % b    # +
```

### Relational Mutations

```python
# Original
if (x > 10)

# Mutations
if (x >= 10)    # >
if (x < 10)     # >
if (x <= 10)    # >
if (x == 10)    # >
if (x != 10)    # >
```

### Logical Mutations

```python
# 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

```python
# 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

```bash
# 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
```

```bash
# Executar mutation testing
mutmut run

# Ver resultados
mutmut results

# Mostrar mutants sobrevientes
mutmut show survived
```

```python
# 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 < 0:
        raise ValueError("Percentual não pode ser negativo")
    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%"""
    assert calcular_desconto(100, 10) == 90.0

def test_desconto_zero():
    """Testa que desconto de 0% mantém valor"""
    assert calcular_desconto(100, 0) == 100.0

def test_desconto_100_porcento():
    """Testa que desconto de 100% 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% deve lançar erro"""
    with pytest.raises(ValueError):
        calcular_desconto(100, 110)
```

### Java com PITest

```xml
<!-- pom.xml -->
<plugin>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-maven</artifactId>
    <version>1.15.8</version>
    <dependencies>
        <dependency>
            <groupId>org.pitest</groupId>
            <artifactId>pitest-junit5-plugin</artifactId>
            <version>1.15.8</version>
        </dependency>
    </dependencies>
    <configuration>
        <targetClasses>
            <param>com.example.*</param>
        </targetClasses>
        <targetTests>
            <param>com.example.*Test</param>
        </targetTests>
    </configuration>
</plugin>
```

```bash
# Executar
mvn org.pitest:pitest-maven:mutationCoverage
```

### JavaScript com Stryker

```bash
# 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'
  }
}
```

```bash
# Executar
npx stryker run
```

---

## Interpretando Resultados

### Score de Mutação

```
Mutation Score = (Mutantes Mortos / Total de Mutantes) × 100

Interpretação:
- 90-100%: Excelente (muitos bugs detectados)
- 80-90%: Bom
- 60-80%: Precisa melhorar
- <60%: Suite fraca
```

### Exemplo de Relatório

```
=============== MUTATION TESTING ===============

Total Mutants: 25
Killed: 23
Survived: 2
Timeout: 0
Skipped: 0

Score: 92%

=============== SURVIVING MUTANTS ===============

1. src/calculator.py:32
   - Changed > to >=
   - Still passes: test_desconto_100_porcento
   
2. 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

```python
# Mutation testing complementa coverage
# Coverage 100% não significa testes bons
# Mutation testing revela gaps
```

### 2. Priorize Módulos Críticos

```bash
# Execute mutation testing em módulos críticos
mutmut run --paths src/payment/,src/auth/
```

### 3. Corrija Mutantes Sobrevientes

```python
# 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

```yaml
# 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%: $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%+ é bom, 90%+ é excelente.

**P: Vale o esforço?**  
R: Para código crítico, sim. Para código simples, talvez não.
