Este guia define como aplicar TDD no ciclo de desenvolvimento, garantindo que cada funcionalidade seja criada a partir de testes. O objetivo é assegurar qualidade, legibilidade, cobertura de casos críticos e permitir evolução sem medo de regressões.
Nossos projetos adotam o Jest (para Node/TypeScript) e o PHPUnit (para PHP) como frameworks oficiais de testes. Ambos são rápidos, amplamente suportados e oferecem recursos completos como testes unitários, mocks, spies, relatórios de cobertura e suporte a testes assíncronos, garantindo padronização no desenvolvimento.
Todos os testes (unitários, de caso de uso e de integração) devem ser escritos utilizando o framework correspondente à linguagem do módulo, assegurando consistência, facilidade de manutenção e integração com o pipeline de CI/CD.
1. Princípios de TDD
- Red → Escreva primeiro um teste que falhe (sem implementação).
- Green → Implemente o mínimo de código necessário para o teste passar.
- Refactor → Refatore o código mantendo os testes verdes.
2. Pirâmide de Testes
- Testes Unitários → rápidos, pequenos e isolados (domínio e funções puras).
- Testes de Caso de Uso / Aplicação → validam regras de negócio e orquestração.
- Testes de Integração → validam integrações com infraestrutura (banco, APIs externas).
3. Fluxo de Desenvolvimento com TDD
- Definir o Requisito → escreva um teste que descreve a regra de negócio esperada.
- Teste Unitário (Domínio) → garantir que a regra isolada funciona.
- Teste de Caso de Uso → validar orquestração da lógica (com mocks de dependências).
- Teste de Integração → validar persistência e contratos externos.
- Teste de Aceitação / API (quando necessário) → validar resposta real da aplicação.
4. Frameworks Oficiais
- Jest → para módulos Node.js/TypeScript.
- PHPUnit → para módulos PHP.
Ambos são obrigatórios para manter padronização e integração com o pipeline de CI/CD.
5. Exemplos
5.1 Teste Unitário (Domínio)
Jest
// tests/user.spec.ts
import { User } from "../src/domain/User";
describe("User Entity", () => {
it("deve calcular idade em dias corretamente", () => {
const user = new User("Sergio", 30);
expect(user.ageInDays()).toBe(30 * 365);
});
});
PHPUnit
// tests/UserTest.php
<?php
use PHPUnit\\Framework\\TestCase;
use App\\Domain\\User;
class UserTest extends TestCase
{
public function testAgeInDays(): void
{
$user = new User("Sergio", 30);
$this->assertEquals(30 * 365, $user->ageInDays());
}
}
5.2 Teste de Caso de Uso
Jest
// tests/create-user.usecase.spec.ts
import { InMemoryUserRepository } from "../src/infrastructure/InMemoryUserRepository";
import { CreateUserUseCase } from "../src/application/usecases/CreateUserUseCase";
it("deve criar usuário válido", async () => {
const repo = new InMemoryUserRepository();
const useCase = new CreateUserUseCase(repo);
const result = await useCase.execute({ name: "Sergio", age: 30 });
expect(result).toMatchObject({ name: "Sergio", age: 30 });
expect(await repo.findAll()).toHaveLength(1);
});
PHPUnit
// tests/CreateUserUseCaseTest.php
<?php
use PHPUnit\\Framework\\TestCase;
use App\\Application\\UseCases\\CreateUserUseCase;
use App\\Infrastructure\\InMemoryUserRepository;
class CreateUserUseCaseTest extends TestCase
{
public function testCreateValidUser(): void
{
$repo = new InMemoryUserRepository();
$useCase = new CreateUserUseCase($repo);
$result = $useCase->execute(["name" => "Sergio", "age" => 30]);
$this->assertEquals("Sergio", $result["name"]);
$this->assertCount(1, $repo->findAll());
}
}
5.3 Teste de Integração
Jest
// tests/user-repository.integration.spec.ts
import { Database } from "../src/infrastructure/Database";
import { UserRepository } from "../src/infrastructure/UserRepository";
import { User } from "../src/domain/User";
it("deve persistir e recuperar usuário do banco", async () => {
const db = new Database(":memory:");
const repo = new UserRepository(db);
const user = new User("Sergio", 30);
await repo.save(user);
const found = await repo.findById(user.id);
expect(found).toMatchObject({ name: "Sergio", age: 30 });
});
PHPUnit
// tests/UserRepositoryIntegrationTest.php
<?php
use PHPUnit\\Framework\\TestCase;
use App\\Infrastructure\\Database;
use App\\Infrastructure\\UserRepository;
use App\\Domain\\User;
class UserRepositoryIntegrationTest extends TestCase
{
public function testPersistAndRetrieveUser(): void
{
$db = new Database(":memory:");
$repo = new UserRepository($db);
$user = new User("Sergio", 30);
$repo->save($user);
$found = $repo->findById($user->getId());
$this->assertEquals("Sergio", $found->getName());
}
}
6. Boas Práticas
- Isolamento: testes unitários nunca devem acessar banco, rede ou APIs externas.
- Cobertura: priorizar cobertura em regras de negócio e casos críticos.
- Nomenclatura: descreva a expectativa no nome do teste (
deve_criar_usuario_valido). - Mocks/Stubs: use apenas quando necessário, não abuse.
- CI/CD: todos os testes devem rodar automaticamente no pipeline.
- Caso seja necessário, incluir no github em Secrets and variables as credenciais existentes no projeto, para os testes serem efetuados
