Tag Archives: teste unitário

Beware the locale

See-ming Lee 李思明 SML Photo

Today I was programming a toString method for a class widely used in a application, using the very useful String.format that provides a C’s like printf formatter.

@Override
public String toString() {
   return String.format("VO[a: %.1f, b: %.1f, c: %.1f]", a, b, a+b);
}

%.1f means a float with one digit precision after the dot separator. The code produces something like:

VO[a: 1.0, b: 2.0, c: 3.0]

The problem arises when running a JUnit test on this method wrote using a regular expression to extract the values from the String to test it correctness. We cannot assume that the dot will be always the separator for displaying a float value, in my locale pt_BR would be a comma. So the output would be:

VO[a: 1,0, b: 2,0, c: 3,0]

For a predictable output we can set a Locale for String.format:

Locale en = new Locale("en");
return String.format(en, "VO[a: %.1f, b: %.1f, c: %.1f]", a, b, a+b);

So it will always use the dot as common separator. Of course you should follow and respect the localization and internationalization efforts in others moments but in this toString case we are using it internally for debug and unitary testing so we can set a English default locale for safety reasons.

Teste unitário automático em Python

Suponha que um cliente (um cliente muito estranho) te pediu para implementar uma função que calculasse o número de fibonacci.

Então você criou o seguinte código (em Python):

def fibo(n):
   if n < 2:
      return n
   else:
      return fibo(n-1) + fibo(n-2)

Devido ao alto número de chamadas recursivas você nunca criaria esse código. Ele só está aí a título de ilustração. =)

O código está pronto, será que acabou o serviço? Como saber se os requisitos foram atendidos? Os testes unitários podem te ajudar. Basicamente você o comportamento da função para certas entradas e se elas se comportarem bem para essas entradas você admite que ela está correta. Por exemplo, você sabe que fibonacci de 1 é 1 e que o fibonacci de 10 é 55. O que você faria normalmente seria abrir um console Python ou criaria um programa de teste para testar esses valores.

O problema é que depois de um tempo alguém pode alterar a função que você criou. Ou pior, alguém pode alterar um código numa outra parte do programa que o seu código usava para fazer o serviço. Um bom discípulo da engenharia de software iria fazer novos testes unitários toda vez que alguém mexer no código, mas um discípulo ninja mesmo, vai automatizar essa tarefa.

Se você estiver trabalhando com Python há pelo menos duas formas fáceis de você automatizar essa tarefa usando os módulos doctest ou unittest. Esse módulos já vem com o Python por padrão.

Doctest

Com o doctest você só cria testes unitários simplesmente adicionando algumas linhas de comentários ao seu código e depois fazendo uma chamada ao doctest:

def fibo(n):
   """
   >>> fibo(0)
   0
   >>> fibo(1)
   1
   >>> fibo(10)
   55
   """
   if n < 2:
      return n
   else:
      return fibo(n-1) + fibo(n-2)
 
import doctest
doctest.testmod()

As últimas duas linhas do programa importam o módulo doctest e pedem para testar aquele programa. Se você salvar o programa como fibo.py e executa-lo, nada aparecerá. Isso é bom, quer dizer que você fez tudo certinho. Vamos supor que eu alterei a função fibo de forma que fibo(10)=20, ou seja, um erro. Então você obtem esse comportamento:

$ python fibo.py
****************************
File “fibo.py”, line 7, in __main__.fibo
Failed example:
fibo(10)
Expected:
55
Got:
20
****************************
1 items had failures:
1 of 3 in __main__.fibo
***Test Failed*** 1 failures.

Excelente não é? Se você quiser um relatório mais detalhado use a opção verbose do doctest usando a linha de comando python fibo.py -v.

Unittest

Como o nome já diz, o módulo unittest foi feito para fazer testes unitários. Com ele você pode fazer coisas mais avançadas além de testar coisas como função(parâmetro) = resultado_esperado. na verdade o módulo unittest é um framework para testes unitários.

No exemplo, vamos importar a função fibo do arquivo fibo.py e depois criar uma clase do tipo TestCase.

import unittest
from fibo import fibo
 
class testa_fibonacci(unittest.TestCase):
   def teste_um(self):
      self.assertEqual(fibo(0),0)
 
   def teste_dois(self):
      self.assertEqual(fibo(1),1)
 
   def teste_tres(self):
      self.assertEqual(fibo(7),13)
 
   def teste_quatro(self):
      self.assertEqual(fibo(10),55)
 
unittest.main()

A execução dele seria assim:

$ python teste.py
….
———————————————————————-
Ran 4 tests in 0.001s

OK

Isso foi só uma brevíssima introdução sobre essas ferramentas. Elas conseguem fazer coisas que você nem imagina. Se você se interessa no assunto eu recomendo fortemente a leitura da documentação do módulo unittest.

Para saber mais: Quality Control in Python, Documentação oficial do unittest e Documentação oficial do módulo doctest.