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.
Capítulo 1: Introdução, configuração e Hello World
Capítulo 2: Organizando as dependências e requerimentos
Capítulo 3: Configurando o pytest e nosso primeiro teste Estamos aqui
Capítulo 4: Configurando o Makefile
Capítulo 5: Adicionando o MongoDB
Capítulo 6: Criando e testando o modelo de usuários
Capítulo 7: Criando usuários
Capítulo 8: Listando usuários
Capítulo 9: Buscando usuários
Capítulo 10: Editando um usuário
Capítulo 11: Deletando um usuário
Capítulo 12: Autênticação por JWT
Capítulo 13: Criando um container Docker
Capítulo 14: Arquivos de configuração para Deploy na Digital Ocean
Capítulo 15: Automatizando o processo de deploy com Fabric
Capítulo 16: CI e CD com Jenkins, Python, Flask e Fabric
Capítulo 17: Utilizando o RabbitMQ com Flask e Sendgrid para enviar e-mails de boas vindas e ativar a conta do usuário
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 .
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