Cuide bem do seu domínio! Parte 3

Seguindo com o projeto baseado no TDD, vamos continuar os testes restantes e finalizar a implementação das regras de negócio na camada de domínio.

Até aqui testamos apenas as especificações da entidade Aluno que eram: Ter idade entre 6 e 18 anos e CPF único na base.
Utilizamos Rhino Mock onde criamos um repositório falso simulando uma consulta de CPF existente e Specification Pattern com o pacote DomainValidation. Talvez não houvesse necessidade aplicar specification pattern para um projeto pequeno. A ideia é apenas mostrar como podemos implementá-lo para atender n regras de negócio de forma simples.

Vamos agora validar as entidades Curso e Matricula. Para ambos, iremos utilizar a técnica de Mock para simular um retorno do banco de dados.
Curso deve possuir código único e uma matrícula não pode conter o mesmo aluno para o mesmo curso.

Passo 19 - Na pasta \Interfaces\Repository\ - crie duas interfaces - ICursoRepository e IMatriculaRepository. Estas interfaces irão herdar de IRepository e implementar métodos específicos.

TDD-ASP-NET-10

ICursoRepository.cs

1
2
3
4
public interface ICursoRepository : IRepository
{
    Curso ObterPorCodigoCurso(string codigo);
}

IMatriculaRepository.cs

1
2
3
4
public interface IMatriculaRepository : IRepository
{
    Matricula ObterMatriculaAlunoCurso(Guid aluno, Guid curso);
}

Passo 20 - Na pasta \Specifications\Cursos - crie a classe CursoUnicoSpecification.cs. Crie também a classe MatriculaUnicaSpecification.cs na pasta \Specifications\Matriculas
TDD-ASP-NET-11

CursoUnicoSpecification.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CursoUnicoSpecification : ISpecification
{
    private readonly ICursoRepository _cursoRepository;
 
    public CursoUnicoSpecification(ICursoRepository cursoRepository)
    {
        _cursoRepository = cursoRepository;
    }
 
    public bool IsSatisfiedBy(Curso curso)
    {
        return _cursoRepository.ObterPorCodigoCurso(curso.CodigoCurso) == null;
    }
}

A classe acima retornará null quando não encontrar o Código do curso na base. O processo é o mesmo do CPF.
MatriculaUnicaSpecification.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MatriculaUnicaSpecification : ISpecification
{
    private readonly IMatriculaRepository _matriculaRepository;
 
    public MatriculaUnicaSpecification(IMatriculaRepository matriculaRepository)
    {
        _matriculaRepository = matriculaRepository;
    }
 
    public bool IsSatisfiedBy(Matricula matricula)
    {
        return _matriculaRepository.ObterMatriculaAlunoCurso(matricula.AlunoId, matricula.CursoId) == null;
    }
}

A diferença na classe acima é que estamos passando o Guid de Aluno e Curso como parâmetro.

Passo 21 - Basta criar as classes que retornam a coleção de erros com base nas especificações de cada entidade. No nosso projeto, temos poucas especificações para Curso e Matricula, mas poderíamos ter n especificações para cada entidade.
Criar as classes - \Validations\Cursos\CursoCadastroValidation.cs e \Validations\Matriculas\MatriculaCadastroValidation.cs

TDD-ASP-NET-12

CursoCadastroValidation.cs

1
2
3
4
5
6
7
8
public class CursoCadastroValidation : Validator
{
    public CursoCadastroValidation(ICursoRepository cursoRepository)
    {
        var cursoUnico = new CursoUnicoSpecification(cursoRepository);
        base.Add("cursoUnicoRule", new Rule(cursoUnico, "Código de curso já está cadastrado!"));
    }
}

MatriculaCadastroValidation.cs

1
2
3
4
5
6
7
8
public class MatriculaCadastroValidation : Validator
{
	public MatriculaCadastroValidation (IMatriculaRepository matriculaRepository)
	{
		var matriculaUnica = new MatriculaUnicaSpecification(matriculaRepository);
		base.Add("matriculaUnicaRule", new Rule(matriculaUnica, "Aluno já possui matrícula para esse curso!"));
	}
}

Nas classes acima, Herdamos a classe Validator do pacote DomainValidation, passamos a classe desejada(Aluno ou Curso), e injetamos dependência do reposítório no construtor. Instanciamos a especificação que desejamos validar, MatriculaUnicaSpecification, e a Rule retornará a mensagem de erro caso a spec não seja atendida.

Dá pra validar de outra forma? Sim uai!!! Coloca um monte de if e vamo que vamo... Lembrando, o uso do specification pattern é apenas sugestivo. Particularmente eu gostei bastante dessa abordagem, pois facilita bastante quando precisamos criar dezenas de regras.

Passo 22 - No projeto de teste crie as classes MatriculaCadastroTest.cs e CursoCadastroTest.cs na pasta \Validation\

TDD-ASP-NET-13

CursoCadastroTest.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[TestClass]
public class CursoCadastroTest
{
    public Curso Curso { get; set; }
 
    [TestMethod]
    public void Curso_Validation_True()
    {
        Curso = new Curso()
        {
            CodigoCurso = "2EF2016"
        };
        var stubRepository = MockRepository.GenerateStub();
        stubRepository.Stub(s => s.ObterPorCodigoCurso(Curso.CodigoCurso)).Return(null); // curso não encontrado - null
 
        var cursoValidation = new CursoCadastroValidation(stubRepository);
        Assert.IsTrue(cursoValidation.Validate(Curso).IsValid);
    }
    [TestMethod]
    public void Curso_Validation_False()
    {
        Curso = new Curso()
        {
            CodigoCurso = "2EF2016"
        };
        var stubRepository = MockRepository.GenerateStub();
        stubRepository.Stub(s => s.ObterPorCodigoCurso(Curso.CodigoCurso)).Return(Curso); // curso encontrado
 
        var cursoValidation = new CursoCadastroValidation(stubRepository);
        Assert.IsFalse(cursoValidation.Validate(Curso).IsValid);
    }
}

MatriculaCadastroTest.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[TestClass]
public class MatriculaCadastroTest
{
    public Matricula Matricula { get; set; }
 
    [TestMethod]
    public void Matricula_Validation_True()
    {
        Matricula = new Matricula()
        {
            AlunoId = new Guid("b2c7e6da-4086-4bf3-9c2f-225852da34c3"),
            CursoId = new Guid("b7b6c5a1-2cea-4ee2-9967-b34d4f76ece6")
        };
        var stubRepository = MockRepository.GenerateStub();
        stubRepository.Stub(s => s.ObterMatriculaAlunoCurso(Matricula.AlunoId, Matricula.CursoId)).Return(null);
 
        var matriculaValidation = new MatriculaCadastroValidation(stubRepository);
        Assert.IsTrue(matriculaValidation.Validate(Matricula).IsValid);
    }
 
    [TestMethod]
    public void Matricula_Validation_False()
    {
        Matricula = new Matricula()
        {
            AlunoId = new Guid("b2c7e6da-4086-4bf3-9c2f-225852da34c3"),
            CursoId = new Guid("b7b6c5a1-2cea-4ee2-9967-b34d4f76ece6")
        };
        var stubRepository = MockRepository.GenerateStub();
        stubRepository.Stub(s => s.ObterMatriculaAlunoCurso(Matricula.AlunoId, Matricula.CursoId)).Return(Matricula);
 
        var matriculaValidation = new MatriculaCadastroValidation(stubRepository);
        Assert.IsFalse(matriculaValidation.Validate(Matricula).IsValid);
    }
}

Caso queira entender melhor as classes acima, na parte 2 desse tutorial temos a explicação baseada nos testes da entidade Aluno. O escopo é o mesmo.

Executando todos os testes nesse momento, teremos o resultado na imagem abaixo:
TDD-ASP-NET-14

Resumo dos testes realizados até aqui:
Aluno_Consistente_False - faz a validação do CPF e da Idade digitados pelo usuário simulando dados inválidos.
Aluno_Consistente_True - faz o mesmo, simulando dados válidos.
Percebam que nesses dois métodos, fizemos uma primeira validação apenas com os dados informados.

Já nas próximas classes precisamos simular uma consulta no banco para validação antes de cadastrar na base, fazendo uso do falso repositório com Rhino Mock.
Aluno_Validation_False - Simula CPF existente na base retornando o próprio objeto que foi instanciado.
Aluno_Validation_True - Simula CPF inexistente com retornou null na consulta.
Os testes de Curso e Matricula seguem o mesmo escopo desses dois últimos.

Esse tutorial vai ficar maior do que eu imaginava. Estou disponibilizando o código fonte gerado até aqui no Github
Precisamos ainda finalizar as regras de negócio da Matrícula que serão validadas nas classes de serviço do domínio. E só para nos situarmos, vejam na imagem em qual camada estamos trabalhando dentro da arquitetura proposta pelo DDD.

A validação das regras de uma matrícula por faixa etária, conforme proposto nesse tutorial, será feita nas classes de serviços do domínio. Vamos trabalhar nelas no próximo post.

Código Fonte EscolaTDD - Parte 3 / GitHub

Vou deixar aqui novamente a referência desse projeto que está baseado nos padrões DDD, TDD, SOLID e Specification pattern.

Tutorial - Asp.Net DDD - Eduardo Pires
Tutorial SOLID - Eduardo Pires

Até mais!

Leave a Reply

Your email address will not be published. Required fields are marked *

This blog is kept spam free by WP-SpamFree.