Testando seu código

../_images/34435687940_8f73fc1fa6_k_d.jpg

Testar o seu código é muito importante.

Getting used to writing testing code and running this code in parallel is now considered a good habit. Used wisely, this method helps to define your code’s intent more precisely and have a more decoupled architecture.

Algumas regras gerais de teste:

  • Uma unidade de teste deve se concentrar em um pequeno número de funcionalidades e provar que tudo está correto.
  • Cada unidade de teste deve ser totalmente independente. Cada teste deve ser capaz de executar sozinho, e também dentro do conjunto de teste, independentemente da ordem em que são chamados. A implicação desta regra é que cada teste deve ser carregado com um novo conjunto de dados e talvez seja necessário fazer alguma limpeza depois. Isso geralmente é gerenciado pelos métodos setUp() e tearDown().
  • Tente arduamente fazer testes que funcionem rapidamente. Se um único teste precisa de mais de alguns milissegundos para executar, o desenvolvimento será diminuído ou os testes não serão executados com a frequência desejável. Em alguns casos, os testes não podem ser rápidos porque eles precisam de uma estrutura de dados complexa para trabalhar, e esta estrutura de dados deve ser carregada toda vez que o teste é executado. Mantenha esses testes mais pesados em um conjunto de teste separado executado por alguma tarefa agendada e execute todos os outros testes sempre que necessário.
  • Aprenda suas ferramentas e aprenda como executar uma única prova ou um caso de teste. Então, ao desenvolver uma função dentro de um módulo, execute os testes dessa função freqüentemente, idealmente automaticamente quando você salvar o código.
  • Execute sempre o conjunto de teste completo antes de uma sessão de codificação, e execute novamente depois. Isso lhe dará mais confiança de que você não quebrou nada no resto do código.
  • É uma boa ideia implementar um hook que executa todos os testes antes de enviar o código para um repositório compartilhado.
  • Caso estejas no meio de uma sessão de desenvolvimento e tiver que interromper o seu trabalho, é uma boa ideia escrever um teste de unidade quebrado sobre o que deseja desenvolver em seguida. Ao retornar ao desenvolvimento, você terá um ponteiro para onde estavas e voltará ao percurso mais rapidamente.
  • O primeiro passo quando você estiver depurando o seu código é escrever um novo teste identificando o bug. Embora nem sempre seja possível, esses testes de detecção de falhas estão entre os mais valiosos itens de código do seu projeto.
  • Use nomes longos e descritivos para testar funções. O guia de estilo aqui é ligeiramente diferente do código de execução, onde os nomes curtos são frequentemente preferidos. O motivo é que as funções de teste nunca são chamadas explicitamente. square() ou mesmo sqr() está ok no código em execução, mas no código de teste você teria nomes como test_square_of_number_2(), test_square_negative_number(). Esses nomes de função são exibidos quando um teste falhar e deverá ser o mais descritivo possível.
  • When something goes wrong or has to be changed, and if your code has a good set of tests, you or other maintainers will rely largely on the testing suite to fix the problem or modify a given behavior. Therefore the testing code will be read as much as or even more than the running code. A unit test whose purpose is unclear is not very helpful in this case.
  • Another use of the testing code is as an introduction to new developers. When someone will have to work on the code base, running and reading the related testing code is often the best thing that they can do to start. They will or should discover the hot spots, where most difficulties arise, and the corner cases. If they have to add some functionality, the first step should be to add a test to ensure that the new functionality is not already a working path that has not been plugged into the interface.

O Básico

unittest

unittest is the batteries-included test module in the Python standard library. Its API will be familiar to anyone who has used any of the JUnit/nUnit/CppUnit series of tools.

Criar casos de teste será realizado escrevendo uma subclasse unittest.TestCase.

import unittest

def fun(x):
    return x + 1

class MyTest(unittest.TestCase):
    def test(self):
        self.assertEqual(fun(3), 4)

A partir do Python 2.7 o unittest também inclui seus próprios mecanismos de descoberta de teste.

Doctest

The doctest module searches for pieces of text that look like interactive Python sessions in docstrings, and then executes those sessions to verify that they work exactly as shown.

Doctests have a different use case than proper unit tests: they are usually less detailed and don’t catch special cases or obscure regression bugs. They are useful as an expressive documentation of the main use cases of a module and its components. However, doctests should run automatically each time the full test suite runs.

A simple doctest in a function:

def square(x):
    """Return the square of x.

    >>> square(2)
    4
    >>> square(-2)
    4
    """

    return x * x

if __name__ == '__main__':
    import doctest
    doctest.testmod()

When running this module from the command line as in python module.py, the doctests will run and complain if anything is not behaving as described in the docstrings.

Ferramentas

py.test

py.test is a no-boilerplate alternative to Python’s standard unittest module.

$ pip install pytest

Despite being a fully-featured and extensible test tool, it boasts a simple syntax. Creating a test suite is as easy as writing a module with a couple of functions:

# content of test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

and then running the py.test command:

$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.1
collecting ... collected 1 items

test_sample.py F

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================

is far less work than would be required for the equivalent functionality with the unittest module!

Hypothesis

Hypothesis is a library which lets you write tests that are parameterized by a source of examples. It then generates simple and comprehensible examples that make your tests fail, letting you find more bugs with less work.

$ pip install hypothesis

For example, testing lists of floats will try many examples, but report the minimal example of each bug (distinguished exception type and location):

@given(lists(floats(allow_nan=False, allow_infinity=False), min_size=1))
def test_mean(xs):
    mean = sum(xs) / len(xs)
    assert min(xs) <= mean(xs) <= max(xs)
Falsifying example: test_mean(
    xs=[1.7976321109618856e+308, 6.102390043022755e+303]
)

Hypothesis is practical as well as very powerful and will often find bugs that escaped all other forms of testing. It integrates well with py.test, and has a strong focus on usability in both simple and advanced scenarios.

tox

tox is a tool for automating test environment management and testing against multiple interpreter configurations.

$ pip install tox

tox allows you to configure complicated multi-parameter test matrices via a simple INI-style configuration file.

mock

unittest.mock é uma biblioteca para a realização de testes em Python. A partir da versão do Python 3.3, está disponível a standard library.

Para as versões mais antigas do Python:

$ pip install mock

It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used.

For example, you can monkey-patch a method:

from mock import MagicMock
thing = ProductionClass()
thing.method = MagicMock(return_value=3)
thing.method(3, 4, 5, key='value')

thing.method.assert_called_with(3, 4, 5, key='value')

To mock classes or objects in a module under test, use the patch decorator. In the example below, an external search system is replaced with a mock that always returns the same result (but only for the duration of the test).

def mock_search(self):
    class MockSearchQuerySet(SearchQuerySet):
        def __iter__(self):
            return iter(["foo", "bar", "baz"])
    return MockSearchQuerySet()

# SearchForm here refers to the imported class reference in myapp,
# not where the SearchForm class itself is imported from
@mock.patch('myapp.SearchForm.search', mock_search)
def test_new_watchlist_activities(self):
    # get_search_results runs a search and iterates over the result
    self.assertEqual(len(myapp.get_search_results(q="fish")), 3)

Mock has many other ways with which you can configure and control its behaviour.