Skip to content

TDD - Test Driven Development

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 falha
def test_sum_empty_list():
assert sum([]) == 0
# Green: implemente apenas o suficiente
def sum(lst):
return 0 if not lst else lst[0] + sum(lst[1:])

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 falha
def test_is_even():
assert is_even(4)
# 2. Green — implementação mínima
def 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) == 0

Para rodar esse teste, precisamos do pacote pytest. Para isso, dentro de um ambiente virtual, vamos fazer sua instalação.

Terminal window
# Cria o ambiente virtual
python3 -m venv venv
# Ativa o ambiente virtual
source ./venv/bin.activate
# Instala o Pytest
python3 -m pip install pytest
# Executa o arquivo com os testes
python3 -m pytest arquivo_com_testes.py

Todos as funções iniciadas com test_ serão testadas pelo PyTest. Aqui podemos seguir com nosso fluxo RGR.

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.

Descrições claras de o que o sistema deve realizar. Cada RF deve virar no mínimo um teste de aceitação.

Terminal window
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):

test_device_api.py
import pytest
from myapp import create_app, db
@pytest.fixture
def 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.fixture
def 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 == 200

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:

test_performance.py
import pytest
from 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 < 50

Vamos analisar agora alguns tipos de testes.

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"] == 2

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 == 200
  • TO-DO
Gai Sensei and Rock Lee Trainning Smiles and Taijutsu
  • TO-DO
Gai Sensei and Rock Lee Trainning Smiles and Taijutsu