🧪 CAPÍTULO 17: ESTRATÉGIAS DE TESTE E AUTOMAÇÃO


🎯 1. Objetivos de Aprendizagem & Competências

Estimativa de Dedicação: 2 horas de estudo autoguiado.
Ao final deste capítulo, você será capaz de:

  • 🔹 Dominar a Pirâmide de Testes de Mike Cohn: Testes Unitários (base rápida), Testes de Integração (meio com banco em memória) e Testes E2E (topo).
  • 🔹 Diferenciar Testes de Caixa-Preta (baseados em requisitos/comportamento) de Testes de Caixa-Branca (baseados na cobertura estrutural de código e branches).
  • 🔹 Aplicar técnicas de Mocking, Test Fixtures e injeção de dependências no pytest.
  • 🔹 Implementar testes automatizados de endpoints HTTP com o test_client nativo do Flask e Pytest.

🏢 2. Cenário Corporativo & Estudo de Caso (TecProExpress)

Na TecProExpress, após uma atualização na rotina de cálculo de cubagem de fretes, o faturamento gerou R$ 45.000,00 em cobranças a menor antes que alguém percebesse o erro em produção. A equipe realizava apenas testes manuais clicando na interface do sistema.

O Desafio: Como Engenheiro de Testes de Software, você deve abolir testes manuais em rotinas críticas, construindo uma suíte automatizada com Pytest que execute 100% dos testes unitários e de integração em menos de 10 segundos a cada commit.


🧠 3. Fundamentação Teórica & Modelos Visuais

3.1. A Pirâmide de Testes de Mike Cohn

Estrutura recomendada para maximizar a velocidade de feedback e minimizar o custo de manutenção:

flowchart TD
    subgraph PIRAMIDE ["PIRÂMIDE DE TESTES DE SOFTWARE"]
        E2E["🌐 1. Testes Ponta a Ponta (E2E / UI)<br>Lentos, caros, poucos (Topo)"]
        INT["⚙️ 2. Testes de Integração (APIs & DB em memória)<br>Velocidade média, validam contratos (Meio)"]
        UNIT["🧪 3. Testes Unitários (Funções, Classes & Regras Puras)<br>Milissegundos, baratos, milhares (Base da Pirâmide)"]
        
        E2E --> INT --> UNIT
    end
    
    style E2E fill:#fee2e2,stroke:#ef4444
    style INT fill:#fef3c7,stroke:#d97706
    style UNIT fill:#dcfce7,stroke:#16a34a

3.2. Caixa-Preta vs. Caixa-Branca

CritérioTeste de Caixa-Preta (Funcional)Teste de Caixa-Branca (Estrutural)
Conhecimento InternoNão conhece o código-fonte (testa entradas e saídas).Analisa o código-fonte, branches e loops.
Técnicas ComunsParticionamento em Classes de Equivalência, Análise de Valor Limite.Cobertura de Sentenças (Statement Coverage), Cobertura de Decisão/Branch.
FocoO sistema atende aos Requisitos Funcionais acordados?Todos os caminhos lógicos do algoritmo foram executados?

💻 4. Aplicação Prática & Código Executável (Python 3.11+)

📋 Pré-requisitos e Instalação

Instale o Flask e o Pytest no seu ambiente virtual:

pip install flask pytest

Dica do Especialista

O test_client() do Flask é nativo e não requer clientes HTTP adicionais (como httpx), tornando a suíte de testes extremamente rápida, isolada e sem dependências assíncronas.

💻 Código Completo e Autocontido (test_api_integracao.py)

Implementação de testes de integração com o test_client nativo do Flask e fixtures do Pytest:

"""
Módulo: test_api_integracao.py
Domínio: Testes de integração de endpoints REST com Flask test_client e Pytest.
"""
import pytest
from flask import Flask, request, jsonify

app = Flask(__name__)

BANCO_DADOS_MOCK = {}

@app.post("/itens/")
def criar_item():
    dados = request.get_json()
    if not dados:
        return jsonify({"detail": "Payload JSON ausente"}), 400

    nome = dados.get("nome")
    preco = dados.get("preco", 0.0)

    if not nome:
        return jsonify({"detail": "Nome é obrigatório"}), 400
    if preco <= 0:
        return jsonify({"detail": "Preço deve ser positivo."}), 400

    BANCO_DADOS_MOCK[nome] = preco
    return jsonify({"status": "CRIADO", "item": nome, "preco": preco}), 201

@app.get("/itens/<nome>")
def consultar_item(nome: str):
    if nome not in BANCO_DADOS_MOCK:
        return jsonify({"detail": "Item não encontrado."}), 404
    return jsonify({"item": nome, "preco": BANCO_DADOS_MOCK[nome]}), 200

# --- SUÍTE PYTEST ---
@pytest.fixture
def client():
    """Fixture Pytest que fornece o cliente de testes isolado do Flask."""
    app.config["TESTING"] = True
    BANCO_DADOS_MOCK.clear()
    with app.test_client() as client:
        yield client

def test_criar_item_com_sucesso(client):
    resposta = client.post("/itens/", json={"nome": "Notebook", "preco": 3500.00})
    assert resposta.status_code == 201
    assert resposta.get_json()["item"] == "Notebook"

def test_criar_item_preco_invalido_deve_retornar_400(client):
    resposta = client.post("/itens/", json={"nome": "Mouse", "preco": -10.00})
    assert resposta.status_code == 400
    assert "Preço deve ser positivo" in resposta.get_json()["detail"]

def test_consultar_item_inexistente_deve_retornar_404(client):
    resposta = client.get("/itens/ItemFantasma")
    assert resposta.status_code == 404

def test_fluxo_completo_cadastro_e_consulta(client):
    post_res = client.post("/itens/", json={"nome": "Teclado", "preco": 250.00})
    assert post_res.status_code == 201
    
    get_res = client.get("/itens/Teclado")
    assert get_res.status_code == 200
    assert get_res.get_json()["preco"] == 250.00

if __name__ == "__main__":
    print("🧪 Executando suíte com Pytest...")
    pytest.main(["-v", __file__])

🚀 Como Executar

Você pode executar o arquivo diretamente com o interpretador Python ou através do runner do Pytest:

# Execução direta via Python
python test_api_integracao.py

# Ou via runner padrão do Pytest
pytest -v test_api_integracao.py

🖥️ Saída Esperada no Terminal

🧪 Executando suíte com Pytest...
============================= test session starts =============================
rootdir: C:\Temp\portal_mdbook
collected 4 items

test_api_integracao.py::test_criar_item_com_sucesso PASSED               [ 25%]
test_api_integracao.py::test_criar_item_preco_invalido_deve_retornar_400 PASSED [ 50%]
test_api_integracao.py::test_consultar_item_inexistente_deve_retornar_404 PASSED [ 75%]
test_api_integracao.py::test_fluxo_completo_cadastro_e_consulta PASSED   [100%]

============================== 4 passed in 0.05s ==============================

💡 5. Checkpoint de Engenharia & Boas Práticas

Boas Práticas & Anti-Patterns

  • Testes Independentes (FIRST): Os testes devem ser Fast (rápidos), Isolated (independentes da ordem de execução), Repeatable (repetíveis em qualquer ambiente), Self-validating (passa/falha sem inspeção manual) e Timely (escritos no momento certo).
  • Anti-Pattern Cone de Sorvete (Ice Cream Cone): Ter milhares de testes manuais/E2E lentos e frágeis no topo e quase nenhum teste unitário na base. Inverta para o formato da Pirâmide!

🔗 6. Conexão com os Projetos Integradores

Projeto IntegradorComo o conceito deste capítulo é aplicado no PI
PI-01 a PI-10 (Todos os 10 PIs)Cada um dos 10 PIs possui uma suíte test_*.py completa com pytest e app.test_client() do Flask executando sobre SQLite em memória.

🧪 7. Quiz de Fixação e Autoavaliação (Formative Assessment)

🧪 Quiz de Autoavaliação — Capítulo 17

1. Na Pirâmide de Testes de Mike Cohn, por que os testes unitários devem compor a maior base da suíte de testes?

  • A) Porque eles são os únicos testes aceitos pela nuvem da AWS.
  • B) Porque são extremamente rápidos (executam em milissegundos), baratos de manter e apontam com precisão exata a linha e função onde o bug ocorreu.
  • C) Porque dispensam a escrita de código-fonte.
  • D) Porque substituem a necessidade de requisitos de negócio.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Testes unitários isolam unidades mínimas de código, fornecendo feedback instantâneo ao desenvolvedor durante a digitação e custando uma fração do tempo de testes E2E.


2. A técnica de teste de Caixa-Preta conhecida como 'Análise de Valor Limite' (Boundary Value Analysis) orienta a testar:

  • A) Apenas números pares e inteiros positivos.
  • B) Os valores nas fronteiras exatas das classes de equivalência (ex: se a regra aceita idades de 18 a 65 anos, testar 17, 18, 19, 64, 65 e 66).
  • C) O consumo máximo de memória do servidor.
  • D) A cor dos botões na interface gráfica.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: A maioria dos erros lógicos de programação ocorre nas bordas das condições relacionais (< vs <=), tornando o teste dos valores limites altamente eficaz.


3. O que é um 'Mock' no contexto de testes automatizados?

  • A) Um bug insolúvel que deve ser ignorado.
  • B) Um objeto simulado que reproduz o comportamento de uma dependência externa (como um banco de dados real ou API externa de pagamentos) de forma controlada e sem chamadas de rede reais.
  • C) Uma falha de sintaxe do Python.
  • D) Um tipo de licença de software livre.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Mocks isolam a unidade sob teste, permitindo simular respostas de sucesso, lentidão ou erros de serviços externos sem custos e sem instabilidades de rede.


🛠️ 8. Ponte para a Ação: Laboratório Prático

🎯 Próximo Passo Prático

Coloque esta teoria em prática executando o roteiro de laboratório autoguiado:
👉 ATIVIDADE 09: QUALIDADE E TESTES COM PYTEST


📌 9. Resumo Executivo & Key Takeaways

  • Pirâmide de Testes: Base ampla de Testes Unitários + Camada intermediária de Integração + Topo enxuto de E2E.
  • Caixa-Preta vs Caixa-Branca: Foco em comportamento funcional e valores limites versus cobertura estrutural de branches.
  • FIRST Principles: Testes devem ser rápidos, isolados, repetíveis, auto-validáveis e tempestivos.
  • Automação no CI: Nenhum código deve ser aceito em produção sem que 100% da suíte de testes automatizados esteja verde.