TDD - Test Driven Development
1. Definição de TDD
Section titled “1. Definição de TDD”Test-Driven Development (TDD) é um método de desenvolvimento de software em que os testes são escritos antes do código de produção, guiando o design e garantindo que cada novo recurso esteja coberto por testes automatizados. Em cada ciclo de TDD, adota-se o fluxo Red–Green–Refactor: inicialmente cria-se um teste que falha (Red), depois escreve-se o código mínimo para fazê-lo passar (Green) e, por fim, refatora-se o código para melhorar estrutura e legibilidade, mantendo todos os testes verdes.
# Red: escreva primeiro o teste que falhadef test_sum_empty_list(): assert sum([]) == 0
# Green: implemente apenas o suficientedef sum(lst): return 0 if not lst else lst[0] + sum(lst[1:])2. Ciclo Red–Green–Refactor
Section titled “2. Ciclo Red–Green–Refactor”O ciclo Red–Green–Refactor é o coração do TDD:
Red: Crie um teste que capture um requisito novo e veja-o falhar. Esse é um dos processos mais trabalhosos, pois aqui você está testando uma funcionalidade que ainda não foi implementada. Utilizar esse momento para verificar se a compreensão dela está correta ou o que pode ser melhorado no processo.Green: Escreva o código mínimo para fazer o teste passar rapidamente. Aqui tem um ponto importante: o código está atendendo o que você especificou como teste. O código está bom para testar, mas ainda não está bom para usar.Refactor: Melhore a estrutura interna (DRY, nomes claros, extração de métodos), mantendo todos os testes passando. Aqui que o código começa a mudar sua interface para melhorar como pode ser implementado.
# 1. Red — teste falhadef test_is_even(): assert is_even(4)
# 2. Green — implementação mínimadef is_even(n): return n % 2 == 0
# 3. Refactor — talvez extrair constantes, adicionar docstring, etc.def is_even(n): """Retorna True se n for par.""" return (n & 1) == 0Para rodar esse teste, precisamos do pacote pytest. Para isso, dentro de um ambiente virtual, vamos fazer sua instalação.
# Cria o ambiente virtualpython3 -m venv venv# Ativa o ambiente virtualsource ./venv/bin.activate# Instala o Pytestpython3 -m pip install pytest# Executa o arquivo com os testespython3 -m pytest arquivo_com_testes.pyTodos as funções iniciadas com test_ serão testadas pelo PyTest. Aqui podemos seguir com nosso fluxo RGR.
3. Levantamento de Requisitos
Section titled “3. Levantamento de Requisitos”Antes mesmo de inciar o processo de escrever testes, é necessário compreender corretamente o que está sendo desenvolvido como sistema. É necessário compreender quais são os requisitos estabelecidos para o sistema em questão.
3.1 Requisitos Funcionais - RF
Section titled “3.1 Requisitos Funcionais - RF”Descrições claras de o que o sistema deve realizar. Cada RF deve virar no mínimo um teste de aceitação.
Funcionalidade: Gestão de Dispositivos Cenário: Cadastrar novo dispositivo Dado que não exista dispositivo com ID "D100" Quando eu enviar POST /devices com { "id": "D100", "tipo": "sensor" } Então recebo status 201 E GET /devices/D100 retorna { "id": "D100", "tipo": "sensor" }Podemos entender que os rRequisitos funcionais descrevem o comportamento ou as funcionalidades específicas que um sistema deve oferecer, capturando as operações que usuários ou sistemas externos podem realizar. Eles devem ser claros, testáveis e rastreáveis até necessidades de negócio.
Alguns exemplos:
- CRUD de entidades: criar, ler, atualizar e excluir registros de clientes, produtos, dispositivos, etc.
- Autenticação e autorização: login de usuários, gestão de perfis de acesso.
- Regras de negócio: cálculos de descontos, fluxos de aprovação, geração de relatórios.
Vamos agora analisar o contexto de um sistema de gestão de dispositivos IoT deve permitir o cadastro de novos dispositivos e recuperação de dados.
-
RF-01: “Ao enviar POST /devices com { id, tipo, localizacao }, deve retornar 201 Created e persistir o dispositivo.”
-
RF-02: “GET /devices/{ id } deve retornar 200 OK com { id, tipo, localizacao, status }.”
Criando um teste para esse cenário (considerando o PyTest e o Flask):
import pytestfrom myapp import create_app, db
@pytest.fixturedef client(): app = create_app(test_config=True) with app.test_client() as client: with app.app_context(): db.create_all() yield client
def test_register_device_and_retrieve(client): # RF-01: cadastro payload = {"id": "dev123", "tipo": "sensor", "localizacao": "sala"} resp = client.post("/devices", json=payload) assert resp.status_code == 201
# RF-02: recuperação resp = client.get(f"/devices/{payload['id']}") assert resp.status_code == 200 data = resp.get_json() assert data["tipo"] == "sensor" assert data["localizacao"] == "sala"Aqui uma observação importante: o que é uma fixture.
Um fixture é uma função Python decorada com @pytest.fixture que prepara o ambiente ou fornece dados para os testes, e pode ainda realizar limpeza após o uso. Pytest “injeta” o valor retornado pelo fixture em qualquer função de teste cujo parâmetro tenha o mesmo nome da fixture .
Exemplos de uso: conexão a banco de dados em memória, inicialização de clientes HTTP, montagem de dados de teste, criação de objetos complexos.
Definimos um fixture com uma função com o decorador.
import pytest
@pytest.fixturedef usuario_admin(): # setup: criar um usuário admin de teste user = User(name="admin", role="admin") db.session.add(user) db.session.commit() yield user # teardown: remover após o teste db.session.delete(user) db.session.commit()Utilizamos essa fixture onde essas informações forem necessárias:
def test_acesso_dashboard(usuario_admin): resp = client.get("/dashboard", headers={"Authorization": f"Bearer {usuario_admin.token}"}) assert resp.status_code == 2003.2 Requisitos Não Funcionais - RNF
Section titled “3.2 Requisitos Não Funcionais - RNF”Atributos de qualidade (performance, segurança, escalabilidade). Cada RNF pode virar testes de carga, benchmark ou análises estáticas. Exemplo de RNF: “Tempo de resposta máximo de 100 ms para leitura de sensor em carga de 1 000 clientes simultâneos.”
Os requisitos não funcionais (RNFs) definem propriedades de qualidade do sistema—como ele deve realizar suas funções—englobando performance, segurança, usabilidade, confiabilidade, escalabilidade e manutenibilidade. Alguns exemplos:
- Performance: tempo de resposta < 100 ms sob carga de 1 000 usuários simultâneos .
- Confiabilidade: disponibilidade ≥ 99,9 % e recuperação em caso de falha em max. 1 min.
- Segurança: criptografia TLS, autenticação OAuth2.
- Escalabilidade: suportar aumento de 50 % de carga sem degradação de performance.
- Usabilidade: interface responsiva em dispositivos móveis.
Pensando em um exemplo:
-
RNF-01 (Performance): “O endpoint GET /devices/{ id }/metrics deve responder em até 50 ms sob 500 requisições concorrentes.”
-
RNF-02 (Segurança): “Toda comunicação deve usar TLS 1.2+ e autenticação via token JWT.”
Exemplo de teste implementado com Python:
import pytestfrom myapp import create_app
@pytest.fixture(scope="session")def client(): app = create_app(test_config=True) return app.test_client()
def test_metrics_endpoint_performance(benchmark, client): # Warmup client.get("/devices/dev123/metrics")
# Benchmarks: executa várias vezes medindo tempo result = benchmark(lambda: client.get("/devices/dev123/metrics")) # Verifica que a média esteja abaixo de 50 ms assert result.stats.mean * 1000 < 504. Construção de Testes
Section titled “4. Construção de Testes”Vamos analisar agora alguns tipos de testes.
4.1 Testes Unitários
Section titled “4.1 Testes Unitários”Validam componentes isolados (funções, classes). Devem ser rápidos, determinísticos e ter escopo mínimo. Boas práticas:
- Nome descritivo (test_user_created_with_valid_email);
- Evitar dependências externas (mock de I/O, DB) ;
- Escrita mínima para passar (minimally passing test).
def test_add_item_to_cart(): cart = Cart() cart.add("apple", 2) assert cart.items["apple"] == 24.2 Testes de Integração
Section titled “4.2 Testes de Integração”Verificam a interação entre módulos ou serviços. Geralmente usam ambientes controlados (DB em memória, containers). Objetivo: detectar falhas de comunicação e mapeamento de dados entre camadas.
def test_register_and_retrieve_device(client): client.post("/devices", json={"id": "X1", "type": "actuator"}) resp = client.get("/devices/X1") assert resp.status_code == 2005. Verificação e Validação
Section titled “5. Verificação e Validação”- TO-DO
6. Referências
Section titled “6. Referências”- TO-DO