Back-End

15 abr, 2009

TDD (Test Driven Development) em ASP/VBScript

Publicidade

Este artigo trata de uma idéia que acredito ser um dos conceitos-chave da
programação moderna. Quando escrevo programação moderna, estou me referindo às
metodologias que reapareceram
com nomes bonitinhos e muita força durante a segunda metade dos anos 90 e início do século 21 em
resposta aos burocráticos, lentos e pesadamente regulamentados métodos em vigência
na época. São exemplos delas: ASD(Adaptive Software Development),
DSDM (Dynamic Systems Development Method),
Scrum,
XP(Extreme Programming) e companhia.

Essas metodologias, que até 2001 eram conhecidas como lightweight methods e depois se tornaram Agile Software Development, têm alguns aspectos em comum. Primeiro, elas “nasceram” da necessidade dos desenvolvedores de se
focarem mais no produto com o qual estão trabalhando do que nos duros processos relacionados
ao desenvolvimento. Isso quer dizer que, ao contrário daquelas estratégias e especificações
de longo prazo que vislumbravam e documentavam o projeto do começo ao fim, agora o desenvolvimento
era feito em pequenas iterações, com o mínimo de planejamento. Outro fator
importante é que essas metodologias são a favor da comunicação face-to-face em detrimento
da documentação escrita entre os membros da equipe (que são pequenas, de 5 a 9 pessoas).
Terceiro, e o mais importante para os programadores, sob o meu ponto de vista, é a
execução constante de testes automatizados em todas as etapas para garantir a qualidade
de cada pequena unidade de código gerada.

Apesar de ser um tema interessantíssimo, vou parar por aqui minha divagação, pois
não é meu objetivo ensinar técnicas para gerenciamento de equipes de software, e sim
ensinar uma das coisas que todas elas exigirão de vocês. Antes de mais nada,
quero lembrar que um programador sempre vai programar, de modo que não importa muito
qual metodologia a empresa onde ele trabalha adota. Para descontrair, segue abaixo
uma tirinha muito engraçada do Geek Hero Comic
que foi cordialmente cedida pelo autor Salvatore Iovene, para este artigo:

Para os que ainda não sabem, este é o terceiro artigo de uma série
que me comprometi a escrever para o iMasters. Segue abaixo a relação
dos artigos:

  1. ASP, uma tecnologia mal interpretada.
  2. Programação orientada a eventos e lambda function em ASP/VBScript.
  3. TDD (Test Driven Development) em ASP/VBScript.
  4. Linguagens: Baseada em Objetos e Orientada a Objetos.
  5. Orientação a Objetos em VBScript “Hackers way”.
  6. Scripting Components, os “Às” na manga.
  7. Caching, conceito de DRY(Don’t Repeat Yourself) aplicado ao ASP.

Se você estiver lendo um artigo meu pela primeira vez, recomendo enfaticamente
que leia os anteriores primeiro, pois estou tentando levá-los à compreensão de
uma grande abstração, contando uma pequena parte da ideia por vez. Colocarei
links para os temas anteriores para facilitar o acesso, mas utilizem CTRL+Click,
pois não há links de um artigo para seus sucessores.

Test Driven Development

Antes de mais nada, TDD não é apenas um mero método para testar o código e sim
uma filosofia para se desenvolvê-lo. Por que ela é importante? Bom, para se ter
uma ideia, como este estudo
do NIST (National Institute of Standards and Technology) aponta, estima-se que 59,5 bilhões
de dólares ou 0,6% do produto interno bruto (acho que talvez esta porcentagem valha aqui no Brasil também)
são perdidos devido a bugs de software. Este artigo diz muitas coisas interessantes,
por exemplo: “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 infraestrutura de testes que permite a identificação e remoção dos
defeitos de forma mais breve e efetiva”. Mas, espere, não fique arrepiado! Nem tudo é
culpa dos programadores: “Outros fatores que contribuem para os problemas de qualidade
incluem estratégias de marketing, pouca responsabilidade dos distribuidores de software…”,
“O aumento da complexidade dos softwares somado à diminuição da expectativa de vida média do produto,
aumentaram o custo econômico dos erros” e por aí vai… – sugiro enfaticamente a leitura do artigo.

Ok, dada a motivação (é sempre bom estar motivado para aprender algo novo), vamos ao algoritmo
da metodologia (retirado do livro “Test Driven Development: By Example” – Kent Beck) com alguns comentários adicionais:

  1. Adicione um teste
    No TDD, cada novo feature começa com a escrita de um teste. Para escrever um teste, o desenvolvedor precisa compreender claramente as
    especificações e requisitos do feature.
    Ele pode conseguir isto através de “casos de uso” e “relatos de
    usuários” que cubram os requisitos e as exceções. Isto pode também
    gerar variações ou modificações de testes já existentes. Enfim, este é
    um diferencial do TDD, fazer o desenvolvedor se focar nos requisitos
    antes de começar a codificar; uma sutil, mas importante diferença.
  2. Execute todos os testes e falhe
    Esta etapa valida se o teste foi bem codificado e se o novo teste não
    deixa passar o código antigo sem nenhuma alteração. É importante que o
    teste escrito falhe. Caso contrário o teste não tem nenhum valor, ou o feature já estava lá.
  3. Faça uma alteração
    Aqui, deve-se codificar um algoritmo que passe no teste. O código
    escrito nesta etapa não será perfeito e pode, por exemplo, passar no
    teste de uma forma deselegante. Isto ainda é aceitável nesta etapa, já
    que as etapas posteriores tratarão de melhorar e refinar o código.
  4. Execute os testes e passe
    Se todos os testes passarem agora, o programador pode ter certeza que o
    código cumpre todos os requisitos. Este é um bom ponto para começar a
    última etapa do algoritmo.
  5. Reescreva o código
    Agora o código pode ser melhorado o quanto necessário. Re-executando os
    testes, o programador tem a certeza que a reescrita não está afetando
    qualquer funcionalidade existente. O conceito de remover duplicidades é
    importante em qualquer design de software.

Pronto, agora você já conhece os passos dessa metodologia. Para
incluir novas funcionalidades, comece sempre pela etapa número 1.
Recomenda-se que a quantidade de alterações entre os testes nunca seja
muito grande, pois assim o programador pode simplesmente utilizar o undo ao invés de ficar “debugando” o programa para
achar o erro.

Aplicando o conhecimento adquirido

Suponha que precisemos implementar um método novo no nosso programa.
O método se chamará “factorial” e recebe como argumento um número
natural. Sua função é ser equivalente à operação n! da matemática, exceto que deve retornar (-1) quando não conseguir executar a tarefa. Saiba mais sobre a operação fatorial.

Comecemos então, criando nosso teste:

<!--#include file="libs/base.asp"-->
<!--#include file="libs/stringbuilder.class.asp"-->
<!--#include file="libs/unittest.class.asp"-->
<code><pre>
<%

dim Tester : set Tester = new UnitTest

Response.write "Testing" & vbNewline
Response.write "=======" & vbNewline
Response.write Tester.test("factorial", array(-1), -1) & vbNewline
Response.write Tester.test("factorial", array(0), 1) & vbNewline
Response.write Tester.test("factorial", array(1), 1) & vbNewline
Response.write Tester.test("factorial", array(2), 2) & vbNewline
Response.write Tester.test("factorial", array(3), 6) & vbNewline
Response.write Tester.test("factorial", array("string"), -1) & vbNewline
Response.write Tester.test("factorial", array(2.718281828), -1) & vbNewline

Response.write vbNewline

set Tester = nothing

%>
</pre></code>

Este teste deverá retornar:

Testing
=======
Failure: factorial(-1) != (integer)-1. Method returns: (empty)
Failure: factorial(0) != (integer)1. Method returns: (empty)
Failure: factorial(1) != (integer)1. Method returns: (empty)
Failure: factorial(2) != (integer)2. Method returns: (empty)
Failure: factorial(3) != (integer)6. Method returns: (empty)
Failure: factorial("string") != (integer)-1. Method returns: (empty)
Failure: factorial(2.718281828) != (integer)-1. Method returns: (empty)

Que era exatamente o que estávamos esperando, todos os testes
falharam, pois essa função ainda não existe! Como tudo deu errado, a
etapa (2) foi um sucesso, prosseguimos então para o passo (3) e
incluímos a seguinte função:

function factorial(n)
if( lcase(typename(n)) = "integer" ) then
if( n >= 0 ) then
factorial = 1
dim i : for i = n to 2 step (-1)
factorial = factorial * i
next
else
factorial = (-1)
end if
else
factorial = (-1)
end if
end function

Executamos o teste novamente e recebemos:

Testing
=======
Success: factorial(-1) == (integer)-1
Success: factorial(0) == (integer)1
Success: factorial(1) == (integer)1
Success: factorial(2) == (integer)2
Success: factorial(3) == (integer)6
Success: factorial("string") == (integer)-1
Success: factorial(2.718281828) == (integer)-1

Viva! A função, no mínimo, funciona. Agora basta melhorá-la e ir
re-testando para garantir que ela funciona como desejado. Veja a versão
final: 

function factorial(n)
if(vartype(n) <> 2) then factorial = (-1) : exit function
if(n < 0) then factorial = (-1) : exit function
if(n = 0) then factorial = 1 : exit function
factorial = n * factorial(n - 1)
end function

O código acima mostra uma característica muito particular e interessante do VBScript. Não é possível escrevermos:

if( ( vartype(n) <> 2 ) and ( n < 0 ) ) then factorial = (-1) : exit function

Como estamos acostumados em outras linguagens, pois o VBScript não possui short-circuiting logical operations (apesar
de o VB possuir elseor e andalso), que são instruções onde o código
compilado pode pular a análise de uma expressão dependendo do resultado
da expressão anterior. Isto significa que se colocarmos as duas
condições no mesmo if, internamente o compilador faz a análise de ambas
as expressões juntas e imediatamente encontra o erro type mismatch para
dados não inteiros. Portanto quando perguntamos se o dado é inteiro e estamos utilizando on error resume next, ele responde que sim, mesmo quando o conteúdo
é “string”.

Nota: Isso não ocorre devido ao fato do VBScript ser loose typed. Outras linguagens, como o Javascript e Python, interpretam a expressão conjunta corretamente,
da forma que estamos acostumados. Esta é realmente mais uma das falhas do VBScript.

Download

Como nos exemplos utilizei alguns includes do AXE(ASP Xtreme Evolution), preparei este
arquivo zip para download
que contém o teste final e os arquivos necessários para ele funcionar.
Aliás, essa é uma das grandes características do framework: muitos de
seus componentes foram desenvolvidos de forma que não dependem da
infraestrutura completa para rodar. Então, se você precisar de algum
componente que o AXE oferece, por exemplo o upload,
basta copiar os arquivos, suas dependências e pronto! Tudo roda perfeitamente!

Conclusão

Assim como o Toyotismo na história da produção automobilística, que
pregava entre outras coisas: 1) Mecanização flexível em oposição à
rígida automação Fordista; 2) Qualidade total, que implementa o
controle de qualidade por meio de todos os trabalhadores em todos os
pontos do processo produtivo, em oposição à qualidade assegurada por
amostragem; 3) Personificação dos produtos; 4) Just in time, que visa “produzir o necessário, na quantidade necessária e no momento necessário”, revolucionou o processo de
construção, acredito que o TDD seja uma das metodologias mais importantes do desenvolvimento de software, pois ele reduz bastante os erros cometidos durante o
desenvolvimento. Se somarmos a ele os pequenos intervalos entre iterações das técnicas de Agile Software Development, conseguimos personalizar melhor o produto
on-demand, ou seja, just-in-time (assim que as novas
especificações do problema aparecem) assegurando assim uma qualidade
superior no produto final. Para os mais interessados, uma outra leitura
que recomendo é: On the Effectiveness of Test-first Approach to Programming.