1.3.1 Exemplo Simples

Nesta seção, apresentamos um exemplo simples do ciclo TDD ilustrado na Seção 1.3, com base na especificação da sequência de Fibonacci, utilizando por Kent Beck (2003).

Considere a especificação do problema da sequência de Fibonacci apresentada a seguir:

A sequência de Fibonacci é constituída da seguinte forma: 1 1 2 3 5 8 13 21 34 55 e assim por diante. Nela, está presente o seguinte padrão. Um mais um igual a dois. Um mais dois é igual a três. Dois mais três são iguais a cinco. E assim por diante. Ou seja, o sucessor de um número na sequência é formada pela soma dos dois números anteriores a ele.

Todo número na série Fibonacci é de aproximadamente 0,618 a 1 em termos de sua relação com o número após ele. Essa proporção nunca muda porque a proporção permanece a mesma. O interessante da sequência é que ela gera a chamada espiral perfeita, conforme ilustrado na imagem abaixo. (Fonte: https://www.ambroker.com/pt/analysis/blog/sequencia-de-fibonacci/)

Espiral Perfeita gerada pela sequência de Fibonacci

Desse modo, suponha que fossemos implementar uma classe com exemplos matemáticos e um desses exemplos fosse um método que gerasse a sequência de Fibonacci. O código completo do exemplo apresentado abaixo está disponível no GitHub no endereço https://github.com/aurimrv/fibonacci. Os arquivos foram numerados sequencialmente para facilitar a apresentação das mudanças implementadas nos testes e na aplicação em si.

Caso não queira instalar as ferramentas de desenvolvimento em seu computador, é possível acompanhar e/ou realizar a execução pelo CoLab, no link https://colab.research.google.com/drive/1BDFJ_FroYLZFxQB0hhwS1SYESIea5L-K?usp=sharing.

Primeira Rodada do Ciclo de TDD

1º Passo do Ciclo TDD - Escreva um Teste que Falhe

Ao aplicar TDD para essa construção, iniciaríamos a escrita de um caso de teste (tests01.py) que representaria uma história de usuário conforme ilustrado abaixo:

import unittest
from math_samples01 import MathSamples

class FibonacciTest(unittest.TestCase):

    def test_fib01(self):
    	self.assertEqual(MathSamples.fibonacci(0), 0)

O exemplo acima, ilustra o uso de um framework de execução de testes em Python denominado UnitTest. Precisamos dele para conduzir a execução automática de nossos testes. É importante observar que ele não é o único existente para tal finalidade. Temos muitos outros disponíveis para Python. Todo conjunto de teste em UnitTest deve ter nome iniciando com "test", como "tests01.py".

Vamos destrinchar o código acima. Inicialmente, na linha 1, indicamos que iremos fazer uso do unittest. Em seguida, na linha 2, o teste indica que estamos assumindo a existência de um pacote math_samples01 que possui dentro dele uma classe denominada MathSamples. Em seguida, na linha 4, é criada uma classe, denominada FibonacciTest a qual estende a classe unittest.TestCase do framework UnitTest. Na linha 6 é criado um método que representa um caso de teste. Métodos que representam casos de teste devem ter o nome iniciado com test_ pois é desse modo que o framework identifica quais métodos ele deve executar automaticamente. Finalmente, na linha 7, o método desejado é invocado com o valor 0 (MathSamples.fibonacci(0)). Conforme a definição, por definição, o valor da sequência de Fibonacci para o valor de n=0 deveria ser 0. Assim, o método self.assertEquals do framework recebe dois parâmetros que permitem realizar uma comparação do valor obtido com a execução do método da aplicação com o valor esperado conforme a especificação.

A partir do relato acima, observa-se que o caso de teste está guiando a escrita da aplicação. Ou seja, para fazer esse teste passar, os pré-requisitos demandados devem ser implementados para que o interpretador Python não acuse erros de sintaxe por não encontrar os elementos do qual o teste depende. Assim sendo, antes de executar o teste acima, construímos o esqueleto da implementação da classe MathSamples que, conforme pode ser observado no teste, tem o método estático fibonacci que aceita um número como argumento. Essa classe poderia ser especificada conforme abaixo e salva no arquivo chamado de math_samples01.py, salva no mesmo diretório do arquivo tests01.py.

class MathSamples:
	@staticmethod
	def fibonacci(n):
		pass;

Para a execução dos testes, basta executar o comando abaixo:

$ python -m unittest tests01.py 
F
======================================================================
FAIL: test_fib01 (tests01.FibonacciTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/auri/insync/fibonacci/tests01.py", line 7, in test_fib01
    self.assertEqual(MathSamples.fibonacci(0), 0)
AssertionError: None != 0

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

Como era de se esperar, o teste falhou pois a classe em teste ainda não implementa a funcionalidade capaz de fazer o teste passar.

2º Passo do Ciclo TDD - Escreva um Código para Passar o Teste

Em seguida, para passar o teste, iremos iniciar a escrita do código funcional da aplicação de modo a fazer o teste passar. Segundo Beck (2003), devemos perder aqui a menor quantidade de tempo possível para fazer o teste passar. Haverá o momento para melhorias. Por hora, a intenção é apenas a de fazer o teste passar com o menor esforço possível. A nova versão do método fibonacci da classe MathSamples, abaixo (arquivo math_samples02.py), tem esse objetivo.

class MathSamples:
	@staticmethod
	def fibonacci(n):
		return 0;

Observamos que nela, a única alteração foi a de remover o comando pass e o substituir por um return 0 (linha 4). Desse modo, ao repetir a execução do teste (arquivo tests02.py), ele irá passar, conforme apresentado a seguir.

$ python -m unittest tests02.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

3º Passo do Ciclo TDD - Eliminar Redundância

Como o código do método fibonacci da classe MathSamples ainda é bem simples, não há necessidade de execução do passo 3 nesse momento e, desse modo, retorna-se ao 1º passo do ciclo para a escrita de mais testes.

Segunda Rodada do Ciclo de TDD

1º Passo do Ciclo TDD - Escreva um Teste que Falhe

O próximo teste, explora o segundo número da sequência de Fibonacci. Agora, nosso arquivo de teste, nomeado de tests03.py, apresenta o conteúdo abaixo:

import unittest
from math_samples03 import MathSamples

class FibonacciTest(unittest.TestCase):

    def test_fib01(self):
    	self.assertEqual(MathSamples.fibonacci(0), 0)

    def test_fib02(self):
    	self.assertEqual(MathSamples.fibonacci(1), 1)

Ao executar os testes, o segundo (test_fib02) irá falhar, conforme ilustrado abaixo. Ou seja, fibonacci(1), conforme nossa definição, é 1 e o código atual retorna 0.

$ python -m unittest tests03.py
.F
======================================================================
FAIL: test_fib02 (tests03.FibonacciTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/auri/insync/tdd/fibonacci/tests03.py", line 10, in test_fib02
    self.assertEqual(MathSamples.fibonacci(1), 1)
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=1)

2º Passo do Ciclo TDD - Escreva um Código para Passar o Teste

Escrevendo o código que faça o teste passar, a implementação do nosso método fibonacci , fica como se segue (arquivo math_samples04.py):

class MathSamples:
	@staticmethod
	def fibonacci(n):
		if(n == 0):
			return 0
		return 1;

E agora, ao executar os testes (arquivo tests04.py) para essa implementação, ambos os testes passam:

$ python -m unittest tests04.py 
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

3º Passo do Ciclo TDD - Eliminar Redundância

Como o código do método fibonacci da classe MathSamples ainda é bem simples, não há necessidade de execução do passo 3 nesse momento e, desse modo, retorna-se ao 1º passo do ciclo para a escrita de mais testes.

Terceira Rodada do Ciclo de TDD

1º Passo do Ciclo TDD - Escreva um Teste que Falhe

O próximo teste, explora o segundo número da sequência de Fibonacci. Agora, nosso arquivo de teste, nomeado de tests05.py, apresenta o conteúdo abaixo:

import unittest
from math_samples05 import MathSamples

class FibonacciTest(unittest.TestCase):

    def test_fib01(self):
    	self.assertEqual(MathSamples.fibonacci(0), 0)

    def test_fib02(self):
       	self.assertEqual(MathSamples.fibonacci(1), 1)

    def test_fib03(self):
    	self.assertEqual(MathSamples.fibonacci(2), 1)

Ao executar esse teste na implementação math_samples05.py obtemos o resultado abaixo, ou seja, surpreendentemente, o nosso código responde adequadamente para esse novo teste, sem que seja necessário qualquer alteração pois, fibonacci(2) é 1, conforme nossa especificação e é assim que a aplicação se comporta:

$ python -m unittest tests05.py 
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Escrevemos, então, mais um teste, agora para o quarto número da sequência, ou seja, fibonacci(3), o qual deveria ser 2 (arquivo tests06.py, abaixo):

import unittest
from math_samples06 import MathSamples

class FibonacciTest(unittest.TestCase):

    def test_fib01(self):
    	self.assertEqual(MathSamples.fibonacci(0), 0)

    def test_fib02(self):
    	self.assertEqual(MathSamples.fibonacci(1), 1)

    def test_fib03(self):
    	self.assertEqual(MathSamples.fibonacci(2), 1)

    def test_fib04(self):
    	self.assertEqual(MathSamples.fibonacci(3), 2)

Ao executar o teste, a aplicação não se comporta corretamente, conforme apresentado a seguir.

$ python -m unittest tests06.py
...F
======================================================================
FAIL: test_fib04 (tests06.FibonacciTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/auri/insync/tdd/fibonacci/tests06.py", line 16, in test_fib04
    self.assertEqual(MathSamples.fibonacci(3), 2)
AssertionError: 1 != 2

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1)

2º Passo do Ciclo TDD - Escreva um Código para Passar o Teste

Escrevendo o código que faça o teste passar, a implementação do nosso método fibonacci , fica como se segue (arquivo math_samples07.py):

class MathSamples:
	@staticmethod
	def fibonacci(n):
		if(n == 0):
			return 0
		if(n <= 2):
			return 1;
		return 1 + 1;

Após a correção, ao executar o conjunto de teste (arquivo tests07.py), todos eles passam:

$ python -m unittest tests07.py 
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

3º Passo do Ciclo TDD - Eliminar Redundância

Nesse momento, é possível observar o código e eliminar redundância uma vez que, ao retornar 1 + 1, têm-se que esses valores já eram retornados quando se calculou o fibonacci(2) e fibonacci(1), ambos retornavam 1.

Desse modo, o primeiro 1 do return, poderia ser substituído por uma chamada recursiva a fibonacci(n-1) (arquivo math_samples08.py).

class MathSamples:
	@staticmethod
	def fibonacci(n):
		if(n == 0):
			return 0
		if(n <= 2):
			return 1;
		return MathSamples.fibonacci(n-1) + 1;

Em seguida, o segundo 1 pode ser substituído por fibonacci(n-2), conforme código abaixo (arquivo math_samples09.py).

class MathSamples:
	@staticmethod
	def fibonacci(n):
		if(n == 0):
			return 0
		if(n <= 2):
			return 1;
		return MathSamples.fibonacci(n-1) + MathSamples.fibonacci(n-2);

Após as melhorias, o conjunto de teste (arquivo tests09.py) é executado com sucesso, encerrando o ciclo TDD. Obviamente, novos testes podem ser redigidos para verificar se continua funcional.

$ python -m unittest tests09.py 
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

Last updated

Was this helpful?