Desenvolver, Testar, Implantar.
No geral, esse é o início da rotina de desenvolvimento da maioria dos projetos de software que não trabalham com alguma metodologia, seja ela ágil, ou baseada nas burocracias da engenharia de software. Eu digo início, pois após a fase de implantação, fases como ’socorrer bugs imprevistos’, ou ‘não sei o que pode estar acontecendo’ se tornam também presente no desenvolvimento, e o que era pra se tornar um ciclo, acaba se tornando uma curva exponêncial que tende ao infinito.
Esse problema é tão grave, que uma estimativa feita pelo Departamento de Comércio dos EUA em 2002, afirma que falhas de software causam em torno de 60 bilhões de dólares em prejuizo, por ano, para e economia Estadounidense. Não que eu esteja sensibilizado pelo problema que os nossos colegas da parte de cima do hemisfério estão passando. Mas é simples perceber que esse é um problema que também afeta o nosso mercado, pois (1) somos de um país que ainda importa muito quando se fala de software, não somente como produto final, mas também como tecnologias de meio (frameworks, ambientes computacionais e dentre vários outros), e (2) pelo descaso que é dado à etapa de testes. Como agravante, calcula-se que atualmente cerca de 50% dos defeitos são encontrados nas fases finais do projeto, ou após os sistemas começarem a ser usados em produção. O que nos leva a duas grandes perguntas:
- É possível identificar e remover defeitos de software mais cedo e de uma maneira mais eficaz?
- Defeitos são imprevisíveis?
Segundo Myers, a atividade de teste é um elemento crítico para garantia de qualidade do software e pode consumir até 40% do esforço no desenvolvimento desse produto. Nesse caso, não está sendo estimado o esforço gasto pela re-implementação de funcionalidades que foram entregues mas que não estavam de acordo com a especificação. Nesse momento, abro aspas para o apelo do nosso amigo Martin Fowler: “Sempre que estiver tentado a escrever um print(), ou uma expressão de depuração, escreva um teste.”
Junto com o manifesto ágil várias foram as abordagens desenvolvidas na tentativa de agregar maior valor ao processo de desenvolvimento, gerando assim, um produto final com mais qualidade. Dentre essas abordagens, o Test-Driven Development (TDD) vem ganhando bastante notoriedade nos últimos anos. E o porque é simples. Essa técnica, como diversas outras, é baseada em pequenas implementações por ciclo, onde a resolução incial do problema deve começar com uma pergunta, formalizada em teste escrito. Aquele mesmo que antes era deixado para o final do projeto.
Em resumo, o TDD é uma prática que foi elaborada no XP, mas que tem sido adotada rapidamente adotada em várias outras metodologias ágeis. Contudo, o TDD não se trata sobre como testar seu software, mas sim, sobre a elaboração do seu projeto orientado aos testes. Esse ponto é muito importante de se compreender, pois muito tem-se visto que TDD é uma maneira de como testar o software. Errado!
Em poucas palavras o TDD é simplesmente você escrever o código de teste antes de codificar o requisito do programa. A princípio isso pode parecer estranho, mas diversas são as vantagens com a utilização dessa metodologia, como por exemplo:
- Mantém os programadores centrados no que exatamente eles precisam fazer;
- Eliminação dos defeitos mais cedo;
- Refatorações mais seguras: Após uma refatoração, como os testes já estão escritos, será muito mais facil apontar se aquela alteração na funcionalidade apresentou alguma mudança no comportamento da aplicação;
- Os testes, quando bem escritos, são mais simples de se entender do que o próprio código. Dessa maneira, os testes podem servir como documentação para os próprios programadores.
Para tal, o TDD é baseado em três leis básicas:
- Não escreva código de negócios, antes de escrever um teste que falhe
- Escreva somente o suficiente no teste para demonstrar uma falha.
- Escreva somente o suficiente no código para passar no teste.
Note que é frizado o quanto de código que você deve escrever. Essa prática deve ser seguida a risca e seus benefícios vão muito além do que somente a redução de linhas de código, mas também a eliminação da duplicidade, redundância, métodos centralizadores longos, etc.
Voltando ao íncio do texto, o ciclo tradicional que geralmente é assim:

se tornou assim:

Veja que após a implementação do código, a refatoração é necessária sempre. O principal ganho do leitor até esse momento, é entender as principais características do TDD, e abrir o coração e adotar a metodologia
O Próximo passo será aplicar toda essa teoria na prática. Será apresentado um estudo de caso incial, que vai demonstrar como é possível se trabalhar com TDD nos seus projetos. Nesse exemplo, será utilizada a linguagem Python, que por padrão, já possui imbutido uma suite de testes (unittest). Diversos são os tipos de testes que podem ser aplicados, mas nesse contexto, será trabalhado os testes de unidade. Para quem ainda não está familiarizado com a linguagem, indico este livro como uma breve introdução.
Imagine que você está de volta aos seus primeiros passos na programação. Provavelmente, um dos primeiros exercícios que foram codificados por você foi a aquela nossa velha e boa calculadora com as operações aritiméticas básicas. Como o domínio do nosso problema não é muito extenso, em poucos testes você já dar ínicio a todo o processo.
O primeiro passo, é pensar na solução do problema. Esse é simples, implementar alguns métodos que façam a soma, subtração, divisão, etc. Como foi visto, o desenvolvimento começa sempre pelo teste, então vamos lá:
1° Passo: Crie sua classe de teste (testCalc.py)
import unittest
from Calc import Calc
class TestCalc(unittest.TestCase):
def testSoma(self):
calc = Calc()
resultado = calc.soma(5, 10)
self.assertEqual(15, resultado)
if name == ” main ”:
unittest.main()
Nesse momento, para quem não tem muita conhecimento na linguagem, pode ter uma certa dificuldade, se você souber pode passar direto para o 2° Passo. Em bom português, as duas primeiras linhas fazem o import do suite de teste, e da classe que será testada, respectivamente. Uma classe chamada TesteCalc é criada, sendo ela filha da classe TestCase (herança). É criado o método testSoma, e dentro dele é instanciado a classe Calc e é chamado o método que vai finalmente realizar a soma soma. Por fim, é comparado o valor esperado com o valor recebido. As linhas finais são responsáveis pela execução do programa.
2° Passo: Veja o teste falhar
Veja o teste falhar
$ python testCalc.py
Traceback (most recent call last):
File ”testCalc.py”, line 2, in <module>
from Calc import Calc
ImportError: No module named Calc
Nesse momento, o programa é executado o resultado nada mais é, do que o esperado, certo? O Código de negócios ainda não foi criado, logo não existe módulo nenhum a ser importado. Prosseguimos criando o código que está faltando.
3° Passo: Crie a classe da calculadora (Calc.py)
class Calc:
def soma(self, x, y):
return x + y
Lembre-se sempre: Crie somente o código suficiente! Continuamos..
4° Passo: Rode seu teste novamente
$ python testCalc.py
.
——————————————————
Ran 1 test in 0.000s
OK
Pronto, o nosso primeiro requisto implementado com sucesso.
5° Passo: Acrescente um novo teste
De agora em diante, eu deixo esse trabalho de dar vida ao ciclo para vocês.
Depois de diversas implementações, vocês devem ter nas mãos um processo como esse:
O exemplo descrito aqui utilizou a linguagem Python, mas nada impede que você utilize na linguagem que você trabalha mais usualmente, visto que o TDD está mais para um conceito do que para uma tecnologia. Para quem quiser saber mais, o livro Test Driven: TDD and Acceptance TDD for Java Developers apresenta uma base teórica muito mais profunda, e diversos exemplos utilizando a linguagem Java. Livros com foco em outras linguagens eu ainda não encontrei, mas se alguem souber, sinta-se avontade em divulgar por aqui também.
Abraços, e bons códigos a todos.
Gustavo H. L. Pinto