VB .NET - TDD - Test Drive Development (Desenvolvimento orientado por testes)


Apresentando o cenário :

Um estudo NIST (National Institute of Standards and Technology) chegou a seguinte conclusão:

"Mais da metade dos erros não foram encontrados até o final do processo de desenvolvimento ou após a utilização pós-venda do produto"
"Um terço desse prejuízo poderia ser eliminado melhorando-se a infra-estrutura de testes que permite a identificação e remoção dos defeitos de forma mais breve e efetiva".
"O aumento da complexidade dos softwares somado à diminuição da expectativa de vida média do produto, aumentaram o custo econômico dos erros"
(fonte:
TDD (Test Driven Development) em ASP/VBScript)

Veja esta matéria acessada em : http://informatica.terra.com.br/interna/0,,OI501471-EI553,00.html

.....
Por muito tempo, gigantes como a Microsoft realizavam seus testes por conta própria. Mas os analistas dizem que especialistas independentes vêm sendo cada vez mais procurados para garantir a neutralidade do  processo e manter os custos sob controle. Um sinal importante quanto à nova tendência surgiu no ano passado, quando a Aztec Software and Technology Services, da Índia, adquiriu a Disha Technologies, uma empresa de testes de capital fechado, por US$ 12 milhões.
.....

Não faz muito que a tarefa de realizar testes em um software era visto como um trabalho secundário relegado a segundo plano e a iniciantes da equipe afinal o encarregado de 'testar' o software não tinha muito prestígio. Em consequência dessa visão e abordagem o teste era feito de forma incompleta e acaba por deixar os erros fossem encontrados pelo usuário final do produto.

Felizmente essa mentalidade mudou e hoje realizar testes em um produto de software esta sendo encarado com mais seriedade.

Um dos métodos atualmente usados para realizar testes é conhecido como test-drive-development (TDD) ou desenvolvimento-orientado-por-testes onde você escreve os testes no princípio do ciclo de vida do software antes mesmo de escrever qualquer código real, e realiza os testes durante todo o ciclo de vida do software.

Dessa forma os testes são realizados intensivamente e pelos próprios desenvolvedores; o resultado é que na entrega do produto existem poucos erros e o software esta mais funcional e com valor agregado. Visto desta forma podemos encarar o TDD não como uma estratégia de testes mas como uma estratégia do projeto de software.

O TDD faz mais do que apenas encontrar erros mais cedo pois a partir do momento que você tem uma suíte de testes disponíveis para suas classes você se sente mais a vontade para refinar o seu código sem receio de 'quebrar' o código ja produzido o que acaba gerando um código de melhor qualidade e um ganho de produtividade.

Usando TDD

Usando TDD, você inicia escrevendo uma série de pequenas rotinas para testar vários aspectos do seu código. Cada teste é auto-suficiente, então você não tem que testar métodos em uma ordem específica para evitar que um teste afete o outro.

Você pode escrever várias rotinas para testar um método simples (por exemplo, para testar casos extremos, entradas inválidas, etc), mas cada teste é executado em um relativo isolamento. Como cada teste é executado dentro de algum tipo de recipiente e não requer uma interface de usuário específica, não há limite para o número de testes que você pode criar para uma classe.

No núcleo de cada teste estão as Assertions (afirmações). As Assertions são chamadas de métodos de testes para os resultados esperados a partir do código.

Por exemplo, se você estivesse testando um código que soma dois números - digamos 1 mais 1 - você teria uma afirmação (assertion) de que o valor retornado será o resultado esperado (no caso o número 2). Se o valor for o que se espera, então essa parte do teste foi bem-sucedida. Se alguma das afirmações em um teste falhar, então todo o teste falha.

Existem três classes Assert básicas no namespace Microsoft.VisualStudio.TestTools.UnitTesting: Assert, StringAssert e CollectionAssert.

1- A classe Assert fornece uma série de testes básicos através de uma série de métodos estáticos. Alguns dos mais usados são descritos a seguir:

Método Descrição
IsTrue
IsFalse
Assume que algum valor passado é verdadeiro ou falso.
Se não for, então a afirmação falha. Geralmente isso é usado para testar os valores de retorno de métodos que retornam valores booleanos.(true/false)    
AreEqual
AreNotEqual    
Assume que dois valores são iguais (ou não).
Geralmente é usado para verificar o valor de retorno de um método em relação ao valor esperado. Por exemplo,
você poderia usar esse método com um método que adiciona dois números:
Assert.AreEqual (calc.Add (1,1), 2)  
Se o valor de retorno de calc.Add (1,1) é 2, então o teste passa.
AreSame
AreNotSame
Assume que dois objetos são o mesmo (ou não).
Isto significa mais do que apenas ter todas as propriedades com o mesmo valor,
significa que os dois valores estão apontando para o mesmo objeto na memória.
IsNull Assume que o valor de retorno é Nada, (Null), isNothing.
isInstanceOfType
IsNotInstanceOfType 
Assume que o valor de retorno do método é de um determinado tipo de objeto (ou não).
Geralmente é usado quando o método pode retornar uma classe base, ou uma das múltiplas classes filhas. Por exemplo,
você pode ter um método que é definido como retornado um Stream, mas quando chamado pode retornar um Stream,
FileStream, NetworkStream
ou outra classe que herda de Stream. Você poderia então usar esta afirmação se você esperasse
um FileStream como segue: Assert.IsInstanceOfType (obj.OpenStream (), GetType (IO.FileStream))

2- A classe StringAssert é usada para testar valores retornados por strings. Os métodos mais comuns desta classe são:

Método Descrição
StartsWith
EndsWith
Assume que o valor de retorno começa (ou termina) com uma substring particular.
Por exemplo, se você estivesse testando um método de componente de acesso a dados que
deve retornar os resultados de uma pesquisa alfabética, você poderia usar o seguinte afirmação:
Dim dt DataTable = obj.GetFuncionariosSobreNome ("D")
Dim primeiroResultado As String = dt.Rows (0). Item ("sobrenome"). ToString ()
StringAssert.StartsWith (primeiroResultado, "D") 
Contains  Assume que o valor retornado contém alguma substring.
Matches
DoesNotMatch
Assume que o valor testados corresponde(ou não corresponde) a uma determinada expressão regular expressão.

3- A classe CollectionAssert fornece métodos estáticos para testar instâncias de ICollection. Abaixo os métodos mais usados:

Método Descrição
AreEqual
AreNotEqual
Testa se duas coleções (conjuntos) são iguais (ou diferentes).
As duas coleções são iguais se eles contiverem o mesmo número de entradas, com os mesmos valores.
Por exemplo, {1, 2, 3} é igual a {1, 2, 3}, mas não é igual a {1, 3, 2}.
Contains
DoesNotContain 
Usado para detectar a presença (ou ausência) de um determinado item dentro de uma coleção.
Por exemplo, você pode usá-lo para testar os resultados de uma consulta de banco de dados para garantir que
um valor esperado é retornado.
AllItemsAreNotNull   Assume que todos os itens em uma coleção são Nada (Nothing, Null)
AllItemsAreUnique Assume que não há entradas duplicadas na coleção
AreEquivalent
AreNotEquivalent
Testa se duas coleções são os iguais (ou diferentes).
È diferente de AreEqual pois os valores não precisam estar na mesma ordem.
Portanto, {1, 2, 3} é equivalente a dois {1, 2, 3} e {1, 3, 2}.
IsSubsetOf
IsNotSubsetOf
Testa de uma coleção para veificar se ela contém (ou não contém) os itens de uma outra coleção.
AllItemsAreInstancesOfType Assume que a coleção inclui apenas itens do mesmo tipo.
Isto pode ser muito útil ao testar métodos que retornam coleções não genéricas, ou coleções de objetos.

Cada um dos métodos descritos acima tem uma série de sobrecargas. A sobrecarga mais simples apenas toma os parâmetros apropriados. As sobrecargas adicionais retornam uma string para fornecer informações adicionais sobre a falha. Isto inclui um método que permite inserir parâmetros adicionais na mensagem.

Por exemplo: você poderia chamar o método Add descrito anteriormente usando qualquer um destes métodos sobrecarregados:

Assert.AreEqual(calc.Add(1,1), 2)
Assert.AreEqual(calc.Add(1,1), 2, "Valores não são iguaisl")
Assert.AreEqual(calc.Add(1,1), 2, "{0} não é igual a {1}", calc.Add(1,1), 2)

Ferramentas TDD nativas no Visual Studio 2010

Originalmente, as ferramentas de teste só estavam disponíveis nas edições Team System do Visual Studio. Felizmente, a Microsoft percebeu que mais e mais desenvolvedores estavam começando a usar o teste em seu processo de desenvolvimento, e incorporou as ferramentas também para a versão Professional Edition. Isso significa que quase todos os desenvolvedores Visual Basic têm acesso às ferramentas básicas para a adição de testes para sua aplicação.

Então, a menos que você esteja usando o Visual Basic Express Edition , você não tem mais nenhuma desculpa para não usar ferramentas de testes em suas aplicações.

As ferramentas TDD disponíveis no Visual Studio são as seguintes:

1- Novos tipos de projeto que podem ser adicionados à sua solução - Embora você possa adicionar os testes em seus projetos existentes, é uma idéia melhor adicioná-los a um projeto separado. Isso não só mantém os testes logicamente separados, mas também mantém o código para os testes livre do inchaço de tamanho do seu resultado. Para isso você tem o template Test Project:

2- O menu Test do Visual Studio oferece diversas opções para testes, entre elas a janela Test View , que fornece um meio simples de ver os testes em sua solução. A opção New Test oferece diversos tipos de templates de testes conforme a figura abaixo:


Janelas : Test View

O menu Test


Os templates para testes

3- A janela Resultados de teste - Test Results (ver Figura), permite que você veja o resultado de um ou mais testes - Esta é a janela principal, com a qual você vai trabalhar quando testar seus aplicativos. Você vai querer ver todos os símbolos verdes (indicando que o teste "passou") ao lado de cada um dos testes, e se ocorrer um erro, você verá uma mensagem a direita da lista de testes. Note que os símbolos verde (ou vermelho) não aparecem até depois de você ter executado cada teste.

Criando seu primeiro teste unitário

Vamos agora a parte prática que é usar TDD em uma aplicação Visual Basic. E vamos iniciar com um exemplo bem simples.

Abra o Visual Studio 2010 (eu estou usando a versão Ultimate) e no menu File-> New Project selecione o template  Visual Basic -> Windows -> Class Library e informe o nome Macoratti.Calc ou um nome ao seu gosto e clique em OK;

Renomeie o arquivo Class1.vb para Calc.vb na janela Solution Explorer. Vamos criar alguns métodos bem simples para mostrar a técnica TDD.

Defina uma método chamado Media que recebe um array de parâmetros e retorna um valor Double que representa a média destes valores:

Public Class Calc
 

Public Function Media(ByVal ParamArray valores() As Double) As Double

   Dim resultado As Double

   Dim soma As Double
 

   For i As Integer = 0 To valores.Count - 1

      soma += valores(i)

   Next

   resultado = soma / valores.Count

   Return resultado

End Function
 

End Class

Esse será o nosso projeto vamos agora criar o nosso teste.

Criando um Teste

Vamos usar o template Test Project. No menu File-> Add -> New Project selecione o template Visual Basic -> Test -> Test Project e informe o nome Macoratti.Calc.Testes e clique em OK;

Com isso será criada uma única classe, UnitTest1.vb em nossa solução. Vamos excluir este arquivo do projeto de testes e criar uma nova classe:

Clique com o botão direito sobre o projeto Macoratti.Calc.Testes (primeiro exclua a classe UnitTest1.vb) e selecione Add -> New Test;

Na janela Add New Test selecione o template Basic Unit Test e informe o nome MediaTestes,vb e clique no botão OK:

O código inicial para a classe MediaTestes, visto na figura abaixo, fornece a estrutura central necessária para todos os testes:

Como você pode notar a classe de teste é uma classe Visual Basic normal com atributos adicionais.

O atributo <TestClass()> identifica esta classe para a funcionalidade de testes do Visual Studio ele só tem o objetivo de marcar a classe como tendo um ou mais testes.

O atributo <TesteMethod()> identifica um dos testes na classe. Como o atributo anterior ele é apenas um atributo de marcação. Cada um dos testes irão usar este atributo para se auto-identificar e isto permite que você tenha outro método no interior da classe que não seja um teste. Note que cada teste é uma rotina e não uma função.

Agora você esta pronto para criar alguns métodos de testes e uma boa prática é manter os testes simples.

O nome dado aos métodos de testes deve ser significativo de forma que apenas lendo o nome do método você já tenha uma idéia do seu objetivo e propósito sem ter que examinar o código o método para entender o que o método faz. Dessa forma o método de testes chamado TesteParaDivisaoPorZero é muito mais significativo do que apenas Teste1.

Dessa forma vamos definir os seguintes métodos na classe MediaTestes:

Imports System.Text
<TestClass()>
Public Class MediaTestes
    Dim oMacCalc As New Calc
    <TestMethod()>
    Public Sub MediaIntervaloConhecido()
        Assert.AreEqual(oMacCalc.Media(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 5.5, "Media de 1 a 10 não é 5.5")
    End Sub

    <TestMethod()>
    Public Sub MediaDeZeros()
        Assert.AreEqual(oMacCalc.Media(0, 0), 0.0, "Media de Zero não é Zero")
    End Sub
    <TestMethod()>
    Public Sub MediaDeNadaNaN()
        Assert.AreEqual(oMacCalc.Media(), Double.NaN)
    End Sub
    <TestMethod()>
    Public Sub MediaDeValoresMaximosEInfinito()
        Assert.AreEqual(oMacCalc.Media(Double.MaxValue, Double.MaxValue), Double.MaxValue, Double.PositiveInfinity)
    End Sub
End Class

Dessa forma criamos 4 testes, cada um para uma combinação diferente usando parâmetros corretos e incorretos.

O primeiro teste tenta verificar se a função esta funcionando como esperado fornecendo um conjunto de valores e verificando o resultado.

O método Assert.AreEqual usado aqui usa 3 parâmetros:

  1. O método para testar;
  2. O valor do resultado esperado;
  3. A mensagem a ser exibida na interface do usuário se um erro ocorrer;

O segundo teste inicia examinando alguns possíveis cenários de limite; se todos os zeros forem passados o método Media. Note que o valor a ser comparado com o resultado foi escrito como 0.0 , e não apenas 0. O motivo é que indicando apenas 0 ocorreria uma falha quando da tentativa de comparar um Double com um Integer.

Os dois últimos testes verificam outros casos limites. O que ocorre se nenhum valor for passado ? e O que ocorre se você usar valores que somados excedem a capacidade da variável Double  que estamos usando para armazenar o valor da soma ? Ambas as verificações verificam o resultado com o valor conhecido : Double.NaN e Double.PositiveInfinity.

Executando os testes

Após criar os testes vamos executá-los. Selecione Test -> Run -> All Test in Solution para executar todos os testes.

Você também pode selecionar para executar um teste individual em : Test -> Run -> Test in Current Context

 

Com esta ação a sua solução será compilada e os testes executados. Se tudo correr bem você verá na janela Test Results um ícone exibindo uma mensagem de Pending e após alguns segundos passando para Passed conforme mostrada na figura a seguir:

Isto significa que os testes passaram e foram executados sem erros.

Vamos provocar falhas nos testes alterando o método Media para provocar um erro de lógica. Altere o código método conforme abaixo:

Public Class Calc


 
Public Function Media(ByVal ParamArray valores() As Double) As Double


  Dim
resultado As Double
 

  Dim soma As Double


  For
i As Integer = 1 To valores.Count - 1

     soma += valores(i)

  Next


  resultado = soma / valores.Count


  Return
resultado

End Function
 

End Class

Alteramos o valor para o contador iniciar com o valor 1 e não zero. Vamos executar novamente os testes após essa alteração...

Na janela Test Results podemos ver que um teste falhou indicado por Failed e pela mensagem de erro que definimos no teste:

O primeiro teste falhou pois não somamos todos os valores da lista pois iniciamos a partir do segundo valor e o resultado da média será diferente de 5.5.

Com isso apresentei os conceitos básicos sobre TDD , a criação de testes e sua execução. Você pode se aprofundar mais nesse interessante e importante assunto para criar testes robustos e aplicações quase sem erros. (eu disse quase, pois ninguém é perfeito...)

Pegue projeto completo aqui: Macoratti.Calc.zip

"Portanto, se já ressuscitastes com Cristo, buscai as coisas que são de cima, onde Cristo está assentado à destra de Deus." Colossenses 3:1

Referências:


José Carlos Macoratti