Lucas Simon

Web Developer. lucassrod@gmail.com

Série API em Flask - Parte 3 - Configurando o pytest e nosso primeiro teste

Um ponto importante para todo o processo de desenvolvimento de software é a implementação de testes. Claro que nem sempre conseguimos cobrir toda a aplicação com testes unitários porém a idéia aqui é minimizar os erros que podem ocorrer em produção.

Para entendimento, segue os capítulos que serão abordados.

O repositório com todo o código fonte esta aqui. Os capítulos estão em branches.

Pytest

O Pytest é um framework ou uma ferramenta para escrever testes no desenvolvimento de software e bibliotecas em Python. Existem inúmeras outras ferramentas como o nose, unittest, doctest e outras… Dentre elas vejo que o pytest é a mais poderosa.

No capítulo anterior Organizando as dependências e requerimentos, em Dependências de teste, colocamos no requirements/tests.txt o pytest==3.6.1 como dependência. Também podemos instalar via linha de comando pip install -U pytest desde que o ambiente virtual esteja ativo.

Para executar um teste simples criamos um arquivo de exemplo:

# -*- coding: utf-8 -*-
# content of test_sample.py

def func(x):
    return x + 1


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

Em seguida, executando o comando pytest temos:

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item

test_sample.py F                                                     [100%]

================================= 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.12 seconds =========================

Dicas de uso

Separei aqui uma lista de vídeos que irão ajudar a utilizar o pytest.

Skip/select tests

Também é possível executar testes diretamente via linha de comando:

$ pytest tests/auth/

Executa todos os testes da pasta auth


$ pytest tests/auth/test_resources.py

Executa todos os testes do módulo test_resources.


$ pytest tests/auth/test_resources.py::TestAuthenticateUser

Executa todos os teste da classe TestAuthenticateUser.

Ou seja podem haver inúmeras classes de teste e você pode selecionar uma somente via linha de comando.


$ pytest tests/auth/test_resources.py::TestAuthenticateUser::test_raise_exception_payload_is_empty

Executa o teste específico chamado test_raise_exception_payload_is_empty.

Fixtures

Parameters

Depuração de erros

O pytest possui um módulo próprio para fazer depuração do código em Pyhton.

Para isso precisamos importar o módulo import pytest e colocar um ponto de parada pytest.set_trace(). Exemplo:

# -*- coding: utf-8 -*-
import pytest


def func(x):
    pytest.set_trace()

    return x + 1


def test_answer():
    pytest.set_trace()

    assert func(3) == 5


Ao executar o comando $ pytest no terminal, ele irá parar a execução duas vezes. A primeira na função test_answer() e a segunda na função func(x).

Configurando a API

Agora que temos uma visão geral da ferramenta pytest podemos aplicá-la no desenvolvimento da API. Algumas mudanças serão feitas.

Primeiro, no arquivo flask-api-users/setup.py altere o arquivo para:

...
__author__ = 'Lucas Simon'
__author_email__ = 'lucassrod@gmail.com'

# adicione essa lista logo após __author_email__

testing_extras = [
    'pytest',
    'pytest-cov',
]

# adicione as configurações na função setup como abaixo

setup(
    name='api',
    ...
    setup_requires=['pytest-runner'],
    tests_require=['pytest'],
    extras_require={
        'testing': testing_extras,
    },
)

Precisamos agora criar dois arquivos nesse mesmo diretório. O primeiro será o setup.cfg com o conteúdo do [aliases] abaixo:

# lucas @ notebook in flask-api-users
$ cat setup.cfg
[aliases]
test=pytest

Segundo arquivo de configuração será o pytest.ini.

# lucas @ notebook in flask-api-users
$ cat pytest.ini
[pytest]
python_files = tests/*/*.py
norecursedirs = build* .git _build tmp*

A configuração python_files faz com que o pytest busque todos os testes dentro do diretório e subdiretórios da pasta tests/. Existem outras configuração como:

  • Executar em todos os arquivos que começam com tests_*.py

O norecursedirs, faz com que o pytest ignore os testes das pastas ou arquivos atribuídos.

Para maiores informações verifique essa parte da documentação.

Em seguida, criar a classe TestingConfig em config.py. Nós criamos esse arquivo no primeiro artigo da série.

# lucas @ notebook in flask-api-users
$ cat config.py
# -*- coding: utf-8 -*-

# Python
from os import getenv

...

class TestingConfig(Config):
    FLASK_ENV = 'testing'
    TESTING = True


config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'default': DevelopmentConfig
}

No diretório da aplicação crie a seguinte estrutura:

# lucas @ notebook in flask-api-users
$ mkdir tests
$ cd tests
$ touch __init__.py conftest.py
$ mkdir home
$ cd home
$ touch __init__.py test_home.py

Nosso diretório de testes deve ficar assim:

$ tree tests
tests
├── conftest.py
├── home
│   ├── __init__.py
│   └── test_home.py
├── __init__.py

Agora vamos codificar uma fixture no conftest.py

$ cat conftest.py
# -*- coding: utf-8 -*-

# Python
from os.path import dirname, isfile, join

import pytest
from dotenv import load_dotenv

# a partir do arquivo atual adicione ao path o arquivo .env
_ENV_FILE = join(dirname(__file__), '../.env')

# existindo o arquivo faça a leitura do arquivo através da função load_dotenv
if isfile(_ENV_FILE):
    load_dotenv(dotenv_path=_ENV_FILE)

# Cria uma fixture que será utilizada no escopo sessão
# ou seja a cada execução do comando pytest


@pytest.fixture(scope='session')
def client():
    from apps import create_app
    # instancia nossa função factory criada anteriormente
    flask_app = create_app('testing')

    # O Flask fornece um caminho para testar a aplicação
    # utilizando o Werkzeug test Client
    # e manipulando o contexto (configurações)
    testing_client = flask_app.test_client()

    # Antes de executar os testes, é criado um contexto com as configurações
    # da aplicação
    ctx = flask_app.app_context()
    ctx.push()

    # retorna o client criado
    yield testing_client  # this is where the testing happens!

    # remove o contexto ao terminar os testes
    ctx.pop()

Nosso primeiro teste

Para codificar nosso teste e realmente afirmar que nosso endpoint / está funcionando corretamente façamos o seguinte:

Edite o arquivo tests/home/test_home.py e cole o conteúdo abaixo:

# -*- coding: utf-8 -*-

# Python
import json

# O `client` é a fixture que criamos dentro do arquivo conftest.py
# ela é passada por parâmetro na função e pode ser usada dentro do escopo dela


def test_index_response_200(client):
    # Realiza uma requisição HTTP do tipo get para o endpoint /
    response = client.get('/')

    # Verificamos a assertividade do código de resposta da requisição
    # http. Ela deve ser exatamente igual 200 retornando um True para
    # o teste
    assert response.status_code == 200

Antes de executar o teste que criamos, execute o comando abaixo para instalar o pacote API e termos referência para executarmos o teste com nossa suite de testes.

pip install -e .

Fonte

Ao executar o teste teremos a seguinte resposta:

# lucas @ notebook in /workspaces/python/flask-api-users
$ pytest -v
========================================================================== test session starts ==========================================================================
platform linux -- Python 3.6.5, pytest-3.6.1, py-1.5.3, pluggy-0.6.0 -- /workspaces/venvs/flask-api-users/bin/python3
cachedir: .pytest_cache
rootdir: /workspaces/python/flask-api-users, inifile: pytest.ini
plugins: flask-0.10.0, cov-2.5.1
collected 1 item

tests/home/test_home.py::test_index_response_200 PASSED                                                                                                           [100%]

======================================================================= 1 passed in 0.04 seconds ========================================================================

Vamos implementar um novo teste. Pensando no seguinte cenário BDD

Given Luiza está acessando a API, When ela informa a rota/endpoint /, Then api deve responder um objeto com a chave ['hello'], And seu conteúdo deve ser world by apps

Logo, adicione o código para este caso de teste no tests/home/test_resource.py.

def test_home_response_hello(client):
    """
    **Given** Luiza está acessando a API,
    **When** ela informa a rota/endpoint `/`,
    **Then** a api deve responder um objeto com a chave `['hello']`,
    **And** seu conteúdo deve ser `world by apps`
    """
    # Realiza uma requisição HTTP do tipo get para o endpoint /
    response = client.get('/')

    # Utilizamos a função loads do modulo json para retornar um dict
    # para a váriavel data.
    # Precisamos passar por parâmetro para essa função a resposta
    # retornada pelo servidor, através da váriavel response.data
    # e decodificar para utf-8
    data = json.loads(response.data.decode('utf-8'))

    # Fazemos o teste de asserção pela chave 'hello'
    assert data['hello'] == 'world by apps'

Ao executar o pytest -v teremos como resposta:

$ pytest -v
========================================================================== test session starts ==========================================================================
platform linux -- Python 3.6.5, pytest-3.6.1, py-1.5.3, pluggy-0.6.0 -- /workspaces/venvs/flask-api-users/bin/python3
cachedir: .pytest_cache
rootdir: /workspaces/python/flask-api-users, inifile: pytest.ini
plugins: flask-0.10.0, cov-2.5.1
collected 2 items

tests/home/test_home.py::test_index_response_200 PASSED                                                                                                           [ 50%]
tests/home/test_home.py::test_home_response_hello PASSED                                                                                                          [100%]

======================================================================= 2 passed in 0.11 seconds ========================================================================

Conclusão

Os passos que mostrei neste artigo são extensos porém são feitos somente uma vez. Vez ou outra precisará fazer alguma outra configuração mais elaborada para o pytest executar.

Sendo assim, configuramos nossa aplicação e agora podemos desenvolver testes com ela. Quero ressaltar que é muito importante em qualquer projeto de software termos teste e saber testar a aplicação e as funcionalidades que a compõem.

No mais bom estudos.

Próximo artigo: Configurando o Makefile