Pular para o conteúdo principal

Testes Unitários

Para quem é esta página

Engenheiros back-end. Filosofia de testes: Estratégia.

Testando Value Objects

# tests/unit/processos/domain/test_value_objects.py
import pytest
from carla.modules.processos.domain.value_objects import NumeroCAR, CPF, ÁreaTotalHectares

class TestNumeroCAR:
def test_formato_valido_aceito(self):
numero = NumeroCAR(valor="MA-0001234-20240115123456")
assert numero.estado == "MA"

@pytest.mark.parametrize("invalido", [
"MA-123-456", # sequencial curto
"ma-0001234-20240115123456", # UF minúscula
"",
])
def test_formato_invalido_rejeita(self, invalido):
with pytest.raises(ValueError, match="inválido"):
NumeroCAR(valor=invalido)

def test_igualdade_por_valor(self):
a = NumeroCAR(valor="MA-0001234-20240115123456")
b = NumeroCAR(valor="MA-0001234-20240115123456")
assert a == b


class TestCPF:
def test_cpf_valido_aceito(self):
cpf = CPF(valor="529.982.247-25")
assert cpf.valor == "52998224725"

def test_todos_digitos_iguais_rejeitado(self):
with pytest.raises(ValueError, match="todos os dígitos iguais"):
CPF(valor="111.111.111-11")

def test_mascarado_protege_cpf(self):
cpf = CPF(valor="52998224725")
assert cpf.mascarado == "***.***.247-**"

Testando Invariantes do Agregado

# tests/unit/processos/domain/test_entities.py
from uuid import uuid4
from carla.modules.processos.domain.entities import ProcessoCAR
from carla.modules.processos.domain.events import ProcessoSubmetido
from carla.modules.processos.domain.exceptions import (
EstadoInvalidoError, DocumentacaoInsuficienteError, PermissionDeniedError
)

class TestProcessoCAR:
def test_submeter_completo_muda_status(self, processo_completo):
rid = processo_completo.requerente_id
processo_completo.submeter(rid)
assert processo_completo.status.value == "submetido"

def test_submeter_emite_evento(self, processo_completo):
rid = processo_completo.requerente_id
processo_completo.submeter(rid)
assert any(isinstance(e, ProcessoSubmetido) for e in processo_completo.domain_events)

def test_submeter_por_outro_usuario_falha(self, processo_completo):
with pytest.raises(PermissionDeniedError):
processo_completo.submeter(uuid4()) # ID diferente

def test_submeter_sem_matricula_falha(self, processo_sem_matricula):
rid = processo_sem_matricula.requerente_id
with pytest.raises(DocumentacaoInsuficienteError):
processo_sem_matricula.submeter(rid)

def test_resubmeter_processo_falha(self, processo_completo):
rid = processo_completo.requerente_id
processo_completo.submeter(rid)
with pytest.raises(EstadoInvalidoError):
processo_completo.submeter(rid) # já está submetido

Testando Serviços de Domínio

class TestCalculadorAreaReservaLegal:
@pytest.fixture
def calc(self):
return CalculadorAreaReservaLegal()

def test_amazonia_exige_80_porcento(self, calc):
area = ÁreaTotalHectares(valor=100.0)
minima = calc.calcular_area_minima(area, "amazonia_legal")
assert minima.valor == 80.0

def test_mata_atlantica_exige_20_porcento(self, calc):
area = ÁreaTotalHectares(valor=50.0)
minima = calc.calcular_area_minima(area, "mata_atlantica")
assert minima.valor == 10.0

def test_bioma_desconhecido_lanca_erro(self, calc):
with pytest.raises(ValueError, match="Bioma desconhecido"):
calc.calcular_area_minima(ÁreaTotalHectares(valor=100.0), "bioma_xyz")
Fixtures para agregados complexos

Crie conftest.py com fixtures que retornam agregados em estados específicos:

@pytest.fixture
def processo_completo():
return ProcessoCAR(
id=uuid4(), status=StatusProcesso.EM_PREENCHIMENTO,
requerente_id=(rid := uuid4()),
documentos=[
make_documento("matricula_imovel", "valido"),
make_documento("ccir", "valido"),
],
imovel_geometria_definida=True,
)