Com as constantes mudanças no modelo corporativo atual, nunca se teve um ambiente tecnológico tão mutável e adaptável, por tais mutações a possibilidade de se inserir erros e falhas nos sistemas desenvolvidos também aumentou muito. Considerando a economia de tempo e a aderência de negócio, deve-se desenvolver uma aplicação que não contenha funcionalidades e códigos extras inúteis e ainda torná-la completamente eficiente ao negócio para o qual foi criada. Uma das técnicas que visa auxiliar na garantia dessas qualidades é o TDD.

TDD significa Test Driven Development, ou em português Desenvolvimento Dirigido por Testes. É uma técnica de desenvolvimento baseada em um ciclo que visa garantir tais aspectos e é composto por passos, quase que culturais, que devem seguir na seguinte ordem:

  1. Criar um teste que signifique uma regra de negócio.
  2. Executar o teste (obviamente ele irá falhar).
  3. Escrever o seu código até o teste passar.
  4. Refatorar o seu código desenvolvido.
  5. Executar o teste novamente.

O princípio básico do TDD é primeiro a criação do teste e depois a construção do código para atendê-lo, desta forma esta técnica garante que a aplicação só tenha código suficiente para suprir o negócio, e nada mais. Esta técnica também valoriza o desenvolvimento ágil, uma vez que defende a eficiência no desenvolvimento de requisitos. Se alguém diz que usa TDD, mas não começa o seu desenvolvimento pelo teste, está fazendo isso errado.

Mãos à obra

Para deixar mais claro como deve funcionar o desenvolvimento de uma aplicação com TDD, vamos definir algumas regras de negócio sobre as quais criaremos alguns testes e posteriormente o código para atendê-los, tudo isso em C#.

Suponha que tenhamos a entidade Pessoa e que todas as pessoas para serem consideradas válidas devem ter pelo menos nome e CPF. Observe que neste requisito temos restrições e validações, sendo assim, já conseguimos definir o primeiro teste: Dado_Uma_Pessoa.Quando_Crio_Deve_Ser_Valida().

Listagem 1: Teste pessoa válida

[TestClass]
public class Dado_Uma_Pessoa
{
    [TestMethod]
    public void Quando_Crio_Deve_Ser_Valida()
    {
        //arrange:
        string cpf = "12345678909";
        string nome = "Manuel da Silva";

        //act:
        Pessoa pessoa = new Pessoa(cpf, nome);

        //assert:
        Assert.IsNotNull(pessoa);
        Assert.IsTrue(pessoa.Cpf == cpf);
        Assert.IsTrue(pessoa.Nome == nome);
    }
}

Como já era esperado o primeiro erro é o de compilação, pois a classe Pessoa ainda não existe (nem o seu projeto existe ainda).

Segundo foi definido acima, para uma Pessoa poder existir é necessário no mínimo que ela contenha nome e CPF, então obrigatoriamente estes dados devem ser cobrados no construtor da classe e a mesma não deve permitir ser instanciada sem estas características, assim conseguimos garantir que a nossa classe adere ao negócio completamente. Sendo assim, conseguimos encontrar dois novos testes, Nao_Devo_Conseguir_Criar_Sem_Cpf() e Nao_Devo_Conseguir_Criar_Sem_Nome(), onde o esperado vai ser uma exceção impedindo minha ação contraria ao negócio. Note que estamos testando tanto o caminho perfeito pelo qual ocorrem os eventos quanto o caminho contraditório onde ocorrerá a exceção.

Listagem 2: Teste para regra CPF obrigatório

[TestMethod]
[ExpectedException(typeof(Exemplo.Dominio.Excecao.ExcecaoCPFInvalido))]
public void Nao_Devo_Conseguir_Criar_Sem_Cpf()
{
    //arrange:
    var cpf = null;
    string nome = "Fulano da Silva";

    //act:
    Pessoa pessoa = new Pessoa(cpf, nome);

    //assert:
    //O esperado para este teste é uma exceção não deixando eu instanciar
    //a classe Pessoa.
}

Através deste teste conseguimos garantir que nunca existirá uma entidade Pessoa não válida instanciada em todo o nosso sistema, que por sua vez estará totalmente alinhado com o negócio para o qual nossa aplicação foi desenvolvida.

Por fim, e com certeza não menos importante, implementaremos a tão falada e esperada classe Pessoa, e esta deve fazer todos os testes passarem corretamente. Agora é a hora em que se encontra a assertividade no desenvolvimento, pois não se estará desenvolvendo de forma desembestada, se terá um objetivo claro e sistêmico, que é satisfazer as condições dos testes que por sua vez satisfarão os requisitos impostos pelos clientes.

Listagem 3: Implementação da Entidade Pessoa

public class Pessoa : Entidade
{
    public Pessoa(string cpf, string nome)
    {
        if (string.IsNullOrWhiteSpace(cpf))
            throw new Excecao.ExcecaoCPFInvalido("Pessoa.Cpf");

        if (string.IsNullOrWhiteSpace(nome))
            throw new Excecao.ExcecaoParametroInvalido("Pessoa.Nome");

        this.Cpf = (string)cpf;
        this.Nome = nome;
    }

    public virtual string Cpf { get; protected set; }

    public virtual string Nome { get; set; }
      
}

Note também que a classe pessoa foi desenvolvida baseada basicamente nos testes anteriormente e não o oposto. Ela contém somente o código necessário para a correspondência dos testes e o bastante para garantir que a regra de negócio seja satisfeita.

Conclusão

Claro que em um modelo mais complexo todos os testes não irão passar logo de cara, mas antes de refatorar qualquer código, tenha em mente deixar todos os seus testes funcionando e sempre os mantenha assim.

Esta técnica também é uma forma de garantir que após cada alteração todo o sistema ainda continue funcionando, pois seguindo este modelo nunca existirá código implementado sem um teste correspondente à sua execução e seus fluxos alternativos.

Considere também que os testes não devem ser estáticos, pois em dado momento as regras podem mudar e haverá a necessidade de refatoração dos testes, o que desencadeará alterações no código. Inicialmente esta cultura será um pouco complexa de abstrair, mas com certeza a produtividade se tornará mais efetiva.

Obrigado e até o próximo.