TDD e testes de unidade com .NET

TDDNeste post vou falar um pouco sobre TDD – Test-Driven Development. TDD é um assunto muito rico, portanto, voltarei ao tema em novos posts.

Por enquanto, darei um enfoque maior em como começar a trabalhar com TDD na plataforma .NET.

Conceitos básicos

1) TDD é uma técnica de desenvolvimento de software onde nenhum código de produção é escrito sem antes se escrever um teste para ele.

2) O ciclo do TDD consiste em:

  • Red – escreva um teste automatizado que falhe
  • Green – faça o teste passar
  • Refactor – refatore o código para que ele fique o mais simples possível (elimine duplicação)

3) Meta: código limpo que funciona! Por isso, TDD é mais uma prática de design do que uma prática de testes, porque ele te obriga a pensar em como seu código deve funcionar (e interagir com outro código) antes de escrevê-lo.

Frameworks de teste para .Net

Como requisito para trabalhar com TDD, faz-se necessário o uso de testes automatizados. Para tal, existem disponíveis inúmeros frameworks de testes. Em se tratando de testes de unidade, na plataforma .Net, podemos citar o Visual Studio Unit Testing Framework e o NUnit (uma lista completa pode ser encontrada aqui).

O VS Unit Testing, é óbvio, é um framework nativo do Visual Studio e, portanto, é possível rodar os testes de unidade de dentro do próprio VS ou ainda via linha de comando pelo utilitário MSTest.

O NUnit é um framework open-source, muito leve e bastante popular. Possui uma GUI própria para rodar os testes. Isso significa que você terá de configurar o VS para executar o GUI do NUnit para você. Uma outra alternativa é utilizar uma ferramentas de terceiros que permita rodar os testes feitos com o NUnit, como o excelente Resharper.

Citei esses 2 por serem os mais utilizados atualmente.

Exercício Prático

Vamos fazer um exercício bem simples para praticarmos TDD e entendermos o espírito da coisa. Vou utilizar no exemplo o VS Unit Testing. O exercício é: retornar o N-ésimo número da sequência de Fibonacci.

Relembrando: o N-ésimo número de Fibonacci é obtido pela soma dos dois números anteriores da sequência, sendo que o primeiro número é 0 e o segundo é o 1. Sendo assim, o terceiro é 1 (0 + 1), o quarto é 2 (1 + 1) e assim por diante. Teremos então a sequência: 0, 1, 1, 2, 3, 5, 8, 13, 21, …..

Vamos na base dos “baby steps” (passos de bebê, ou seja, seguir em um ritmo bem lento):

1) Sabemos por definição que Fibonacci(0) = 0. Então vamos escrever um teste que atenda essa especificação:

[TestClass]
public class FibonacciTeste
{
   [TestMethod]
   public void OPrimeiroElementoDaSequenciaDeveSer0()
   {
      Assert.AreEqual(0, Fibonacci.Elemento(0));
   }
}

Neste momento, o teste sequer compila pois a classe Fibonacci ainda não foi criada. Como começamos pelo teste, somos “obrigados” a pensar em como queremos utilizar nossa futura funcionalidade. Em outras palavras, estamos pensando do ponto de vista do cliente da classe.

2) Criamos então a classe Fibonacci e o método Elemento, que recebe a posição do número desejado dentro da sequência. Em seguida, implementamos nosso método Elemento da forma mais simples possível para que o teste passe: ROUBANDO. Sendo assim, simplesmente retornamos 0, já que é isso que o teste espera.

public static class Fibonacci
{
   public static int Elemento(int posicao)
   {
      return 0;
   }
}

Você pode estar exclamando: “Que coisa idiota!”. Fique calmo que já já eu falo mais sobre o “roubar”.

3) Neste ponto, nosso teste está passando (Green) e não há nada que possamos fazer para deixar este código mais limpo (Refactor). É óbvio também que nosso método não está pronto, porque ele só retorna o primeiro elemento (posição 0) da sequência. Seguimos em frente então, escrevendo outra especificação:

[TestMethod]
public void OSegundoElementoDaSequenciaDeveSer1()
{
   Assert.AreEqual(1, Fibonacci.Elemento(1));
}

Ao rodar o teste, ele obviamente falha.

4) Voltamos ao método Elemento para atender nosso teste:

public static int Elemento(int posicao)
{
   if (posicao == 0) return 0;
   return 1;
}

Notem acima que tratamos o 0 com o “if” e retornamos o 1 abaixo. Ao rodar o teste, ele passa (assim como o anterior).

5) Vamos para o teste seguinte:

[TestMethod]
public void OTerceiroElementoDaSequenciaDeveSer1()
{
   Assert.AreEqual(1, Fibonacci.Elemento(2));
}

6) E chegamos em algo como:

public static int Elemento(int posicao)
{
   if (posicao == 0) return 0;
   if (posicao == 1) return 1;
   return 1;
}

Percebam que isso não vai chegar ao fim nunca, certo? Mas lembrem-se que: a partir do 3º elemento (posicao=2), o resultado é obtido pela soma dos 2 números anteriores da sequência. Sendo assim, aquele último ‘1’ seria a soma de 0 e 1 (return 0 + 1). Temos então a oportunidade de generalizar nosso método, uma vez que ele já sabe obter os dois primeiros elementos:

public static int Elemento(int posicao)
{
   if (posicao == 0) return 0;
   if (posicao == 1) return 1;
   return Elemento(posicao-2) + 1;
}

Note que o 0 é um exemplo de Elemento(posicao – 2), uma vez que a “posicao” em que estamos é a 2 (terceiro elemento). Rodamos e vemos que os testes continuam passando.

7) Continuamos refatorando. Sabemos que para obter o 1, fazemos Elemento(posicao – 1):

public static int Elemento(int posicao)
{
   if (posicao == 0) return 0;
   if (posicao == 1) return 1;
   return Elemento(posicao-2) + Elemento(posicao-1);
}

E aí está nosso método prontinho. Todo feito a partir dos testes!

Não se sente seguro ainda? Crie testes para mais alguns elementos da sequência e confira!

Retornando constantes e baby steps

Retornar constantes e baby steps, obviamente, não são regras imaculadas do TDD. Conforme você for adquirindo experiência e segurança em seguir adiante, alguns passos podem ser pulados, inclusive começando por uma implementação mais próxima da solução final.

Esses pulos ainda podem ser mais drásticos, caso estejamos utilizando ferramentas de refactoring, como o já citado Resharper.

Concluindo

TDD é uma técnica que ajuda a melhorar o design da aplicação, o que significa aumento de qualidade da mesma. É uma técnica díficil pois exige disciplina para seguirmos seu ciclo e até mesmo para COMEÇARMOS pelos testes.

Dei um caminho aqui para começar a praticar. Agora basta começar. Uma opção é comparecer a Coding Dojos.

Vale lembrar também que TDD é uma técnica de quase uma década e que serviu como base para conceitos mais recentes (e abrangentes) como ATDD e BDD.

4 comentários em “TDD e testes de unidade com .NET

Adicione o seu

  1. Boa tarde Robson,

    Deixa eu tirar uma dúvida com vc, eu li o livro do Kent Beck sobre tdd, pude ver esse exemplo do Fibonacci, o exemplo da moeda, etc….
    A minha pergunta é : Você consegue usar bem o TDD nos seus projetos ?
    Porque mesmo depois de ler o livro e buscar material na internet, ainda não consegui me imaginar utilizando TDD em meus projetos.
    Caso vc use, pode me contar como começou a usar e se realmente melhorou a qualidade dos seus projeto.

    Abraços

    Curtir

    1. Olá, Roger

      Sim, utilizo efetivamente desde 2010. Comecei usando em aplicações novas (que eram parte de uma aplicaçao maior que dava manutenção) E também em um dos módulos legado onde estávamos portando código de algumas stored procedures satânicas para C#.

      O código melhorou sim, pois com TDD você se força a PENSAR na API que você quer, além de criar um código mais desacoplado. E ter uma bateria de testes rodando e VERDES te dá sim uma melhor segurança.

      Agora vem a parte chata: por experiência própria, sei que código bem escrito não vai nascer magicamente por causa do TDD. Vi muito código que nasceu com testes (incluindo alguns feitos com mim) cujo resultado ficou ruim ou no mínimo confuso.

      E agora a segunda parte chata: código de teste também é código! E o problema acima também se repetiu aqui: testes muito grandes, frágeis e confusos de entender.

      Você precisa começar, praticar bastante e buscar conhecimento pra escrever testes decentes, senão você só irá arrumar mais problema e quem já não tava muito afim irá começar a criticar (“tá vendo como esse negócio não funciona?”).

      No entanto, apesar das consideracões acima, não desanime. Experimente e você irá notar que os testes te darão muito mais segurança ao codar.

      Dá uma olhada neste artigo onde falo sobre algumas técnicas para cuidar do código de teste: https://robsoncastilho.com.br/2015/08/18/deixando-seus-testes-mais-legiveis-e-robustos/.

      Que dificuldade voce está tendo pra começar? Por que disse que não se imagina utilizando TDD? Comenta ai.
      []s

      Curtir

  2. Fala Robson, obrigado pela resposta.

    Vamos lá,

    Se a linha de código não for boa, com certeza o TDD não vai melhorá-la.Mas isso por hora não é um dos meus problemas, eu tenho uma boa linha de código, procuro sempre seguir o SOLID e tenho bastante habilidade para aplicar os princípios (inclusive OO).
    O meu problema é que tenho dificuldade em dar os passos curtos na montagem do teste, pois, eu acostumei a criar o metódo antes e em seguida criar o teste unitário.Eu até tento codificar o teste antes, mas acho muito devagar e não consigo organizar meus pensamentos para isso (um passinho de cada vez), dessa forma, eu desisto do tdd e vou direto para o código, e em seguida faço a criação do teste.
    Eu perguntei se você utiliza o TDD porque durante minha experiência profissional nunca encontrei ninguém que utilizasse (se bem que teste unitário tem muitas empresas que ainda não utilizam), com isso, fiquei com a impressão de ser algo que vem sendo divulgado mas que os programadores em geral não colocam em seus projetos.

    Curtir

    1. Oi, Roger

      Acho que é uma questão de adaptação mesmo. Algumas pessoas se adaptam. Outras não. Se você consegue garantir qualidade (bom funcionamento, desacoplamento, facilidade de manutenção, etc..) escrevendo os testes DEPOIS, manda bala.

      Eu me adaptei e acredito que me é bastante útil. É claro que não pratico TDD em 100% dos casos. Não uso TDD pra escrever DAOS ou controllers (MVC), por exemplo. E até mesmo código onde o pratico mais, acabam ficando “rabos” pra depois, em geral, como alguma dívida técnica, por não encontrar uma solução “testável” (isso tem acontecido com mais frequencia com código javascript, onde preciso testar promises, observables, algum tipo de evento…).

      Tenta acompanhar o passo-a-passo do livro do Kent Beck (TDD by Example) ou qualquer artigo semelhante. É preciso disciplina sim, mas acredito que verá benefícios quando estiver notando que seu código, aos poucos, está nascendo e funcionando de verdade (e bem documentado pelos testes)

      Já ensaiei pra postar algo a respeito de TDD (mais ou menos sobre que estamos conversando por aqui), mas estou com pouco tempo pro blog (voce deve ter notado). Espero conseguir tirar do papel, ate pra servir como um resposta mais detalhada para suas dúvidas, que são bastante comuns.

      Obrigado pelas perguntas!
      Qquer coisa, estamos aí!
      []s

      Curtir

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

Blog no WordPress.com.

Acima ↑