<p><strong>Meta Description:</strong> Aprenda TDD do zero: ciclo Red-Green-Refactor, baby steps, mocks e práticas essenciais para escrever código testável e robusto.</p>

<hr>
<h2>O Que é TDD?</h2>

<p>Test-Driven Development (TDD) é uma metodologia de desenvolvimento onde você escreve os testes antes do código de produção. O ciclo é simples: Red (falha), Green (passa), Refactor (melhora).</p>

<h3>Ciclo Red-Green-Refactor</h3>

<pre><code>    RED              GREEN            REFACTOR
     ↓                ↓                 ↓
┌─────────┐     ┌─────────┐      ┌─────────────┐
│Escreva  │     │Faça o   │      │Melhore o    │
│o teste  │ →   │código   │ →    │código sem   │
│que      │     │mínimo   │      │mudar o      │
│falha    │     │passar   │      │comportamento│
└─────────┘     └─────────┘      └─────────────┘
     ↑                                   │
     └───────────────────────────────────┘
                 (Repetir)
</code></pre>

<hr>
<h2>Por Que TDD?</h2>

<h3>Benefícios Quantificados</h3>

<figure class="wp-block-table"><table>
<tr><th>Métrica</th><th>Com TDD</th><th>Sem TDD</th></tr>
<tr><td>Defeitos em produção</td><td>40-60{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} menos</td><td>Baseline</td></tr>
<tr><td>Cobertura de testes</td><td>90-100{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}</td><td>20-50{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}</td></tr>
<tr><td>Confiança em refactoring</td><td>Alta</td><td>Baixa</td></tr>
<tr><td>Tempo de debugging</td><td>30-50{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} menos</td><td>Baseline</td></tr>
<tr><td>Custo de manutenção</td><td>40{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} menor</td><td>Baseline</td></tr>
</table></figure>

<h3>Quando Usar TDD</h3>

<p><strong>TDD BRILHA quando:</strong><br><ul><li>Algoritmos complexos</li><ol><li>APIs e bibliotecas</li><br><li>Refactoring de código legado</li><br><li>Código compartilhado</li><br><li>Requisitos claros</li><br></ol><br><strong>TDD é mais difícil quando:</strong><ol><li>UI layouts complexos</li><br><li>Exploratory features</li><br><li>Requisitos muito instáveis</li><br><li>Prototipagem rápida</li><br></ol><br><hr><br><h2>TDD em Diferentes Linguagens</h2></p>

<h3>Python com pytest</h3>

<pre><code># 1. RED - Escrever teste que falha
import pytest

<p>def test_somar_dois_numeros_positivos():<br>    # Dado<br>    a = 2<br>    b = 3</p>

<p># Quando<br>    resultado = somar(a, b)</p>

<p># Então<br>    assert resultado == 5</p>

<p># 2. GREEN - Implementar código mínimo<br>def somar(a, b):<br>    return 5  # Hardcoded para fazer passar</p>

<p># 3. REFACTOR - Implementar corretamente<br>def somar(a, b):<br>    return a + b<br></code></pre></p>

<h3>Exemplo Completo: Calculadora de Desconto</h3>

<h4>RED - Testes Iniciais</h4>

<pre><code># tests/test_desconto.py
import pytest
from desconto import CalculadoraDesconto

<p>class TestCalculadoraDesconto:<br>    def setup_method(self):<br>        self.calc = CalculadoraDesconto()</p>

<p>def test_desconto_de_10_porcento(self):<br>        """Cliente com 10 compras recebe 10{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} de desconto"""<br>        resultado = self.calc.calcular(valor=100, quantidade_compras=10)<br>        assert resultado == 90</p>

<p>def test_desconto_de_20_porcento(self):<br>        """Cliente com 20 compras recebe 20{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} de desconto"""<br>        resultado = self.calc.calcular(valor=100, quantidade_compras=20)<br>        assert resultado == 80</p>

<p>def test_sem_desconto(self):<br>        """Cliente novo (0 compras) não recebe desconto"""<br>        resultado = self.calc.calcular(valor=100, quantidade_compras=0)<br>        assert resultado == 100</p>

<p>def test_desconto_maximo_de_30_porcento(self):<br>        """Cliente VIP (50+ compras) recebe no máximo 30{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}"""<br>        resultado = self.calc.calcular(valor=100, quantidade_compras=100)<br>        assert resultado == 70</p>

<p>def test_valor_negativo_deve_falhar(self):<br>        """Valor negativo deve lançar exceção"""<br>        with pytest.raises(ValueError):<br>            self.calc.calcular(valor=-100, quantidade_compras=10)</p>

<p>@pytest.mark.parametrize("valor,compras,esperado", [<br>        (100, 5, 100),   # Sem desconto<br>        (100, 10, 90),   # 10{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}<br>        (100, 20, 80),   # 20{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}<br>        (100, 50, 70),   # 30{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}<br>        (200, 10, 180),  # 10{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} em valor maior<br>    ])<br>    def test_parametrizado(self, valor, compras, esperado):<br>        resultado = self.calc.calcular(valor=valor, quantidade_compras=compras)<br>        assert resultado == esperado<br></code></pre></p>

<h4>GREEN - Implementação Mínima</h4>

<pre><code># desconto.py
class CalculadoraDesconto:
    def calcular(self, valor, quantidade_compras):
        if valor &lt; 0:
            raise ValueError(&quot;Valor não pode ser negativo&quot;)

<p>if quantidade_compras &gt;= 50:<br>            desconto = 0.30<br>        elif quantidade_compras &gt;= 20:<br>            desconto = 0.20<br>        elif quantidade_compras &gt;= 10:<br>            desconto = 0.10<br>        else:<br>            desconto = 0</p>

<p>return valor * (1 - desconto)<br></code></pre></p>

<h4>REFACTOR - Melhorias</h4>

<pre><code># desconto.py (refatorado)
from dataclasses import dataclass

<p>@dataclass<br>class RegraDesconto:<br>    min_compras: int<br>    max_compras: int | None<br>    percentual: float</p>

<p>REGRAS = [<br>    RegraDesconto(min_compras=50, max_compras=None, percentual=30),<br>    RegraDesconto(min_compras=20, max_compras=49, percentual=20),<br>    RegraDesconto(min_compras=10, max_compras=19, percentual=10),<br>]</p>

<p>class CalculadoraDesconto:<br>    def calcular(self, valor: float, quantidade_compras: int) -&gt; float:<br>        if valor &lt; 0:<br>            raise ValueError("Valor não pode ser negativo")</p>

<p>for regra in REGRAS:<br>            if quantidade_compras &gt;= regra.min_compras:<br>                if regra.max_compras is None or quantidade_compras &lt;= regra.max_compras:<br>                    return valor * (1 - regra.percentual / 100)</p>

<p>return valor<br></code></pre></p>

<h3>JavaScript com Jest</h3>

<pre><code>// 1. RED - Testes
describe('CalculadoraFrete', () =&gt; {
  const calculadora = new CalculadoraFrete();

<p>test('frete para SP capital', () =&gt; {<br>    expect(calculadora.calcular('SP', 'São Paulo')).toBe(15.90);<br>  });</p>

<p>test('frete para RJ', () =&gt; {<br>    expect(calculadora.calcular('RJ', 'Rio de Janeiro')).toBe(20.00);<br>  });</p>

<p>test('frete para outros estados', () =&gt; {<br>    expect(calculadora.calcular('MG', 'Belo Horizonte')).toBe(30.00);<br>  });</p>

<p>test('frete grátis para SP com compra &gt; 200', () =&gt; {<br>    expect(calculadora.calcular('SP', 'São Paulo', 250)).toBe(0);<br>  });<br>});</p>

<p>// 2. GREEN - Implementação<br>class CalculadoraFrete {<br>  calcular(estado, cidade, valorCompra = 0) {<br>    if (estado === 'SP' &amp;&amp; valorCompra &gt; 200) return 0;<br>    if (estado === 'SP') return 15.90;<br>    if (estado === 'RJ') return 20.00;<br>    return 30.00;<br>  }<br>}<br></code></pre></p>

<h3>Java com JUnit 5</h3>

<pre><code>// Teste
class CarrinhoTest {

<p>@Test<br>    void deveCalcularTotalComDesconto() {<br>        // Given<br>        Carrinho carrinho = new Carrinho();<br>        carrinho.adicionar(new Produto("Camiseta", 50.00, 2));<br>        carrinho.adicionar(new Produto("Calça", 100.00, 1));</p>

<p>// When<br>        double total = carrinho.calcularTotal();</p>

<p>// Then<br>        assertEquals(200.00, total, 0.01);<br>    }</p>

<p>@Test<br>    void deveAplicarCupomDesconto() {<br>        // Given<br>        Carrinho carrinho = new Carrinho();<br>        carrinho.adicionar(new Produto("Camiseta", 100.00, 1));</p>

<p>// When<br>        carrinho.aplicarCupom(new Cupom("DESCONTO10", 10));<br>        double total = carrinho.calcularTotal();</p>

<p>// Then<br>        assertEquals(90.00, total, 0.01);<br>    }</p>

<p>@ParameterizedTest<br>    @CsvSource({<br>        "100, 10, 90",<br>        "200, 10, 180",<br>        "100, 20, 80"<br>    })<br>    void deveCalcularComDescontoParametrizado(<br>        double valor, double desconto, double esperado<br>    ) {<br>        Carrinho carrinho = new Carrinho();<br>        carrinho.adicionar(new Produto("Item", valor, 1));<br>        carrinho.aplicarCupom(new Cupom("TESTE", desconto));</p>

<p>assertEquals(esperado, carrinho.calcularTotal(), 0.01);<br>    }<br>}<br></code></pre></p>

<hr>
<h2>Baby Steps: Implementação Incremental</h2>

<h3>Exemplo: Fibonacci</h3>

<pre><code># Passo 1: O menor teste possível
def test_fibonacci_0():
    assert fibonacci(0) == 0

<p># Implementação mínima<br>def fibonacci(n):<br>    return 0</p>

<p># Passo 2: Segundo valor<br>def test_fibonacci_1():<br>    assert fibonacci(1) == 1</p>

<p>def fibonacci(n):<br>    if n == 0:<br>        return 0<br>    return 1  # Ainda não está correto!</p>

<p># Passo 3: Terceiro valor<br>def test_fibonacci_2():<br>    assert fibonacci(2) == 1</p>

<p># GREEN: Implementação correta (forma recursiva)<br>def fibonacci(n):<br>    if n &lt;= 0:<br>        return 0<br>    if n == 1:<br>        return 1<br>    return fibonacci(n - 1) + fibonacci(n - 2)</p>

<p># GREEN: Implementação melhor (forma iterativa)<br>def fibonacci(n):<br>    if n &lt;= 0:<br>        return 0<br>    if n == 1:<br>        return 1</p>

<p>a, b = 0, 1<br>    for _ in range(2, n + 1):<br>        a, b = b, a + b<br>    return b<br></code></pre></p>

<hr>
<h2>Mocks e Stubs</h2>

<h3>Por Que Usar Mocks?</h3>

<p>Mocks são essenciais para:<ol><li>Isolar unidade sob teste</li><br><li>Simular dependências externas</li><br><li>Controlar cenários de teste</li><br><li>Aumentar velocidade dos testes</li></ol></ul></p>

<h3>Exemplo: Service com Dependência</h3>

<pre><code># 1. Teste com mock
from unittest.mock import Mock, patch

<p>def test_pedido_service_cria_pedido_com_sucesso():<br>    # Given<br>    email_service = Mock()<br>    email_service.enviar = Mock(return_value=True)</p>

<p>payment_gateway = Mock()<br>    payment_gateway.cobrar = Mock(return_value={<br>        "status": "approved",<br>        "transaction_id": "tx_123"<br>    })</p>

<p>repository = Mock()<br>    repository.salvar = Mock(return_value=1)</p>

<p>service = PedidoService(<br>        email_service=email_service,<br>        payment_gateway=payment_gateway,<br>        repository=repository<br>    )</p>

<p># When<br>    resultado = service.criar_pedido(<br>        cliente_id=1,<br>        itens=[{"produto_id": 1, "quantidade": 2}],<br>        forma_pagamento="credit_card"<br>    )</p>

<p># Then<br>    assert resultado["status"] == "confirmed"<br>    assert resultado["pedido_id"] == 1</p>

<p># Verificar chamadas<br>    payment_gateway.cobrar.assert_called_once()<br>    repository.salvar.assert_called_once()<br>    email_service.enviar.assert_called_once()<br></code></pre></p>

<h3>Spy vs Mock vs Stub</h3>

<figure class="wp-block-table"><table>
<tr><th>Tipo</th><th>Comportamento</th><th>Uso</th></tr>
<tr><td><strong>Stub</strong></td><td>Respostas pré-programadas</td><td>Fornecer dados</td></tr>
<tr><td><strong>Mock</strong></td><td>Comportamento verificado</td><td>Verificar interações</td></tr>
<tr><td><strong>Spy</strong></td><td>Wrapper do real + verificação</td><td>Comportamento real + verificação</td></tr>
</table></figure>

<pre><code># Stub
repository = Mock()
repository.buscar_por_id = Mock(return_value={"id": 1, "nome": "Produto"})

<p># Spy (usando wraps)<br>with patch('app.repository') as mock_repo:<br>    mock_repo.buscar_por_id.return_value = {"id": 1}</p>

<p>service = MyService(mock_repo)<br>    service.algum_metodo()</p>

<p># Verificar que foi chamado<br>    mock_repo.buscar_por_id.assert_called_once_with(1)<br></code></pre></p>

<hr>
<h2>TDD em Prática: Kata de String Calculator</h2>

<h3>Requisitos</h3>
<ol><li>Criar calculadora que recebe string vazia retorna 0</li>
<li>Recebe "1" retorna 1</li>
<li>Recebe "1,2" retorna 3</li>
<li>Recebe "1n2,3" retorna 6</li>
<li>Suporta delimitador customizado "//;n1;2" retorna 3</li>
</ol>
<h3>Solução TDD</h3>

<pre><code># Step 1: String vazia
def test_string_vazia_retorna_zero():
    assert string_calculator("") == 0

<p>def string_calculator(numbers):<br>    return 0</p>

<p># Step 2: Um número<br>def test_um_numero():<br>    assert string_calculator("1") == 1</p>

<p>def string_calculator(numbers):<br>    if not numbers:<br>        return 0<br>    return int(numbers)</p>

<p># Step 3: Dois números separados por vírgula<br>def test_dois_numeros():<br>    assert string_calculator("1,2") == 3</p>

<p>def string_calculator(numbers):<br>    if not numbers:<br>        return 0<br>    return sum(int(n) for n in numbers.split(','))</p>

<p># Step 4: Múltiplos números<br>def test_multiplos_numeros():<br>    assert string_calculator("1,2,3,4,5") == 15</p>

<p>def string_calculator(numbers):<br>    if not numbers:<br>        return 0<br>    delimiters = [',', 'n']<br>    parts = numbers.replace('n', ',')<br>    return sum(int(n) for n in parts.split(','))</p>

<p># Step 5: Delimitador customizado<br>def test_delimitador_customizado():<br>    assert string_calculator("//;n1;2;3") == 6</p>

<p>def string_calculator(numbers):<br>    if not numbers:<br>        return 0</p>

<p># Detectar delimitador customizado<br>    if numbers.startswith('//'):<br>        end_header = numbers.index('n')<br>        delimiter = numbers[2:end_header]<br>        numbers = numbers[end_header + 1:]<br>        numbers = numbers.replace(delimiter, ',')</p>

<p>return sum(int(n) for n in numbers.replace('n', ',').split(','))<br></code></pre></p>

<hr>
<h2>Conclusão</h2>

<p>TDD é mais que escrever testes primeiro - é uma filosofia de design que resulta em código mais limpo, testável e manutenível. As chaves para dominar TDD são:<br><ol><li><strong>Prática constante</strong> - Katas diárias</li><br><li><strong>Baby steps</strong> - Pequenas iterações</li><br><li><strong>Feedback rápido</strong> - Execução de testes rápida</li><br><li><strong>Disciplina</strong> - Não pule etapas</li><br><li><strong>Paciência</strong> - Curva de aprendizado é íngreme no início</li><br></ol><br><hr><br><h3>FAQ</h3></p>

<p><strong>P: TDD funciona para todos os projetos?</strong>  <br>R: Não. É excelente para lógica de negócio, APIs. Menos para UI exploratória ou protótipos.</p>

<p><strong>P: Como começar com TDD?</strong>  <br>R: Pratique com katas (String Calculator, Bowling Game, Tennis Refactoring) diariamente.</p>

<p><strong>P: TDD não ralent development?</strong>  <br>R: No curto prazo parece mais lento, mas reduz significativamente bugs e tempo de debugging.</p>

<p><strong>P: Posso usar TDD com legacy code?</strong>  <br>R: Sim! Comece com characterization tests, depois refatore一点点 com testes.</p>