Fundamentos de Testes em Python

Beginner

This tutorial is from open-source community. Access the source code

Introdução

A natureza dinâmica do Python torna os testes de extrema importância para a maioria das aplicações. Não existe um compilador para encontrar seus erros (bugs). A única maneira de encontrar erros é executar o código e garantir que você experimente todos os seus recursos.

Asserções (Assertions)

A instrução assert é uma verificação interna para o programa. Se uma expressão não for verdadeira, ela levanta uma exceção AssertionError.

Sintaxe da instrução assert.

assert <expression> [, 'Diagnostic message']

Por exemplo.

assert isinstance(10, int), 'Expected int'

Ela não deve ser usada para verificar a entrada do usuário (ou seja, dados inseridos em um formulário web ou algo semelhante). Seu propósito é mais para verificações internas e invariantes (condições que sempre devem ser verdadeiras).

Programação por Contrato (Contract Programming)

Também conhecido como Design By Contract, o uso liberal de asserções é uma abordagem para projetar software. Ele prescreve que os projetistas de software devem definir especificações de interface precisas para os componentes do software.

Por exemplo, você pode colocar asserções em todas as entradas de uma função.

def add(x, y):
    assert isinstance(x, int), 'Expected int'
    assert isinstance(y, int), 'Expected int'
    return x + y

Verificar as entradas irá imediatamente detectar chamadores que não estão usando argumentos apropriados.

>>> add(2, 3)
5
>>> add('2', '3')
Traceback (most recent call last):
...
AssertionError: Expected int
>>>

Testes Inline (Inline Tests)

As asserções também podem ser usadas para testes simples.

def add(x, y):
    return x + y

assert add(2,2) == 4

Dessa forma, você está incluindo o teste no mesmo módulo que seu código.

Benefício: Se o código estiver obviamente quebrado, as tentativas de importar o módulo irão travar.

Isso não é recomendado para testes exaustivos. É mais um "teste superficial" básico. A função funciona em algum exemplo? Se não, então algo está definitivamente quebrado.

Módulo unittest

Suponha que você tenha algum código em simple.py.

## simple.py

def add(x, y):
    return x + y

Agora, suponha que você queira testá-lo. Crie um arquivo de teste separado como este em /home/labex/project/test_simple.py.

## test_simple.py

import simple
import unittest

Em seguida, defina uma classe de teste.

## test_simple.py

import simple
import unittest

## Observe que ela herda de unittest.TestCase
class TestAdd(unittest.TestCase):
    ...

A classe de teste deve herdar de unittest.TestCase.

Na classe de teste, você define os métodos de teste.

## test_simple.py

import simple
import unittest

## Observe que ela herda de unittest.TestCase
class TestAdd(unittest.TestCase):
    def test_simple(self):
        ## Test with simple integer arguments
        r = simple.add(2, 2)
        self.assertEqual(r, 5)
    def test_str(self):
        ## Test with strings
        r = simple.add('hello', 'world')
        self.assertEqual(r, 'helloworld')

*Importante: Cada método deve começar com test.

Usando unittest

Existem várias asserções (assertions) embutidas que vêm com unittest. Cada uma delas afirma uma coisa diferente.

## Assert that expr is True
self.assertTrue(expr)

## Assert that x == y
self.assertEqual(x,y)

## Assert that x != y
self.assertNotEqual(x,y)

## Assert that x is near y
self.assertAlmostEqual(x,y,places)

## Assert that callable(arg1,arg2,...) raises exc
self.assertRaises(exc, callable, arg1, arg2, ...)

Esta não é uma lista exaustiva. Existem outras asserções no módulo.

Executando unittest

Para executar os testes, transforme o código em um script.

## test_simple.py

...

if __name__ == '__main__':
    unittest.main()

Em seguida, execute Python no arquivo de teste.

$ python3 test_simple.py
F.
========================================================
FAIL: test_simple (__main__.TestAdd)
--------------------------------------------------------
Traceback (most recent call last):
  File "testsimple.py", line 8, in test_simple
    self.assertEqual(r, 5)
AssertionError: 4 != 5
--------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=1)

Comentário

Testes unitários (unit testing) eficazes são uma arte e podem se tornar bastante complicados para aplicações grandes.

O módulo unittest possui um grande número de opções relacionadas a executores de teste (test runners), coleta de resultados e outros aspectos de teste. Consulte a documentação para obter detalhes.

Ferramentas de Teste de Terceiros (Third Party Test Tools)

O módulo unittest embutido tem a vantagem de estar disponível em todos os lugares - ele faz parte do Python. No entanto, muitos programadores também o consideram bastante verboso. Uma alternativa popular é o pytest. Com o pytest, seu arquivo de teste se simplifica para algo como o seguinte:

## test_simple.py
import simple

def test_simple():
    assert simple.add(2,2) == 4

def test_str():
    assert simple.add('hello','world') == 'helloworld'

Para executar um teste, basta digitar um comando como python -m pytest. Ele então encontrará e executará todos os testes. O módulo pode ser instalado usando pip install pytest.

Há muito mais no pytest do que este exemplo, mas geralmente é bem fácil começar caso você decida experimentá-lo.

Neste exercício, você explorará a mecânica básica do uso do módulo unittest do Python.

Em exercícios anteriores, você escreveu um arquivo stock.py que continha uma classe Stock. Para este exercício, presume-se que você está usando o código escrito para o Exercício 7.9, envolvendo propriedades tipadas (typed-properties). Se, por alguma razão, isso não estiver funcionando, você pode querer copiar a solução de Solutions/7_9 para o seu diretório de trabalho.

Exercício 8.1: Escrevendo Testes Unitários (Unit Tests)

Em um arquivo separado test_stock.py, escreva um conjunto de testes unitários para a classe Stock. Para começar, aqui está um pequeno fragmento de código que testa a criação de instâncias:

## test_stock.py

import unittest
import stock

class TestStock(unittest.TestCase):
    def test_create(self):
        s = stock.Stock('GOOG', 100, 490.1)
        self.assertEqual(s.name, 'GOOG')
        self.assertEqual(s.shares, 100)
        self.assertEqual(s.price, 490.1)

if __name__ == '__main__':
    unittest.main()

Execute seus testes unitários. Você deve obter uma saída semelhante a esta:

.
----------------------------------------------------------------------
Ran 1 tests in 0.000s

OK

Depois de se certificar de que funciona, escreva testes unitários adicionais que verifiquem o seguinte:

  • Certifique-se de que a propriedade s.cost retorna o valor correto (49010.0)
  • Certifique-se de que o método s.sell() funciona corretamente. Ele deve decrementar o valor de s.shares de acordo.
  • Certifique-se de que o atributo s.shares não pode ser definido com um valor não inteiro.

Para a última parte, você precisará verificar se uma exceção é levantada. Uma maneira fácil de fazer isso é com um código como este:

class TestStock(unittest.TestCase):
    ...
    def test_bad_shares(self):
         s = stock.Stock('GOOG', 100, 490.1)
         with self.assertRaises(TypeError):
             s.shares = '100'

Resumo

Parabéns! Você concluiu o laboratório de Testes (Testing). Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.