“`html
Arquitetura que sustenta testes
Dominando a Arquitetura de TDD
Quando eu aplico TDD com disciplina, a arquitetura não “aparece pronta”: ela evolui guiada por exemplos.
O resultado é um sistema com baixo acoplamento, limites claros e testes que protegem comportamento — não implementação.
1) Comece pelos limites: o que entra, o que sai, o que é regra
A “arquitetura de TDD” começa definindo fronteiras. Antes de pensar em classes, eu separo:
entrada (comandos/requests), saída (respostas/eventos) e regra (decisões do domínio).
Isso evita o erro mais comum: criar infraestrutura e depois tentar encaixar testes.
O TDD quer que a regra rode rápido e determinística.
- Use camadas por intenção: controllers/adapters de um lado, aplicação (casos de uso) no meio, domínio (regras) no outro.
- Defina contratos (interfaces/DTOs/valores): testes devem validar contratos claros.
- Trate efeitos colaterais como bordas: persistência, rede e tempo ficam fora do núcleo.
2) Estruture casos de uso por exemplos (e não por entidades)
A cada ciclo do TDD, eu faço uma pergunta: qual comportamento do sistema eu estou descrevendo?
Em vez de organizar por “tabela X” ou “entidade Y”, eu organizo por fluxos.
Isso reduz mudanças futuras quando o modelo do domínio evolui.
- Caso de uso = uma orquestração pequena e explícita: validação, chamadas ao domínio, persistência e resposta.
- Domínio = regras puras (quando possível) que podem ser testadas isoladamente.
- Adapters = implementação dos contratos. Eles trocam sem exigir reescrita do núcleo.
Se eu preciso de muitos mocks por mock, eu provavelmente estou testando implementação e não comportamento.
3) Controle o acoplamento: dependências como portas, não como detalhes
Arquitetura que escala com TDD depende de portas (interfaces) e adapters (implementações).
O teste deve falar com a porta, não com o detalhe.
Em termos práticos, eu defino contratos para o que o caso de uso precisa:
repositórios, geradores de IDs, relógio, serviço externo, mensageria.
O núcleo não “conhece” tecnologia.
- Evite chamar diretamente frameworks (ex.: ORM/HTTP) dentro do núcleo.
- Prefira valores imutáveis (ex.: Money, Email, Status) para reduzir estado surpresa.
- Injeção de dependência mínima: só o que o caso de uso consome.
- Falhas explícitas: erros retornam tipos/resultado; não exceções genéricas espalhadas.
Se eu quero testar persistência/HTTP, eu monto testes separados para o adapter.
4) Refatore com segurança: o ciclo TDD como motor de design
Refatoração no TDD não é um evento; é um modo de trabalho.
Eu mantenho o foco no comportamento e uso o conjunto de testes como “rede”.
Dessa forma, quando a arquitetura precisa evoluir, eu faço isso com mudança pequena por vez.
- Red-Green-Refactor: eu escrevo o teste (Red), implemento o mínimo (Green), e refatoro (Refactor) mantendo verde.
- Sem “pré-arquitetura”: eu não adiciono camadas porque “fica bonito”; eu adiciono porque um teste precisa.
- Refatoração orientada a sinais:
teste lento → limite errado; teste frágil → contrato implícito; duplicação → extração de regra comum.
Se cai gradualmente, minha arquitetura está ajudando; se dispara, a arquitetura está atrapalhando.
JavaScript/TypeScript (ilustrativo)
type Email = string;
class DuplicateEmailError extends Error {
constructor(email: Email){
super(`Email já cadastrado: ${email}`);
this.name = "DuplicateEmailError";
}
}
interface UserRepository {
existsByEmail(email: Email): Promise<boolean>;
save(user: { id: string; email: Email }): Promise<void>;
}
interface IdGenerator {
nextId(): string;
}
interface CreateUserInput {
email: Email;
}
interface CreateUserOutput {
id: string;
}
class CreateUser {
constructor(
private readonly repo: UserRepository,
private readonly ids: IdGenerator
) {}
async execute(input: CreateUserInput): Promise<CreateUserOutput> {
const email = input.email;
const exists = await this.repo.existsByEmail(email);
if (exists) throw new DuplicateEmailError(email);
const id = this.ids.nextId();
await this.repo.save({ id, email });
return { id };
}
}
/**
* Teste de comportamento (conceitual):
* - Quando email existe: deve lançar DuplicateEmailError
* - Quando email não existe: deve persistir e devolver id
*
* O teste conversa com o CreateUser e com portas (UserRepository/IdGenerator),
* não com banco/HTTP.
*/
Quer continuar evoluindo sua arquitetura com TDD?
Se você gostou do enfoque em limites, contratos e refatoração guiada por testes,
leia também outros posts do yurideveloper.com para transformar TDD em um hábito de design — e não só de escrita de testes.
“`
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!