Noções básicas do Pytest
Vamos começar a testar com o Pytest. Como mencionamos na unidade anterior, o Pytest é altamente configurável e pode lidar com conjuntos de testes complexos. Mas não é preciso muito para começar a escrever testes. Na verdade, quanto mais fácil uma estrutura permitir que você escreva testes, melhor.
No final desta seção, você deve ter tudo o que precisa para começar a escrever seus primeiros testes e executá-los com o Pytest.
Convenções
Antes de mergulhar em testes de redação, devemos abordar algumas das convenções de teste nas quais o Pytest se baseia.
Não há regras rígidas sobre arquivos de teste, diretórios de teste ou layouts de teste gerais em Python. Ao conhecer essas regras, você pode aproveitar a descoberta e a execução automáticas de testes sem a necessidade de qualquer configuração extra.
Diretório de testes e arquivos de teste
O diretório principal para testes é o diretório tests . Você pode colocar esse diretório no nível raiz do projeto, mas também não é incomum vê-lo ao lado de módulos de código.
Nota
Neste módulo, usaremos como padrão testes na raiz de um projeto.
Vamos ver como fica a raiz de um pequeno projeto Python chamado jformat
:
.
├── README.md
├── jformat
│ ├── __init__.py
│ └── main.py
├── setup.py
└── tests
└── test_main.py
O diretório tests está na raiz do projeto com um único arquivo de teste. Nesse caso, o arquivo de teste é chamado de test_main.py. Este exemplo demonstra duas convenções críticas:
- Use um diretório de testes para colocar arquivos de teste e diretórios de teste aninhados.
- Arquivos de teste de prefixo com teste. O prefixo indica que o arquivo contém código de teste.
Atenção
Evite usar test
(formulário singular) como o nome do diretório. O test
nome é um módulo Python, portanto, criar um diretório chamado o mesmo o substituiria. Em vez disso, use sempre o plural tests
.
Testar funções
Um dos fortes argumentos para usar o Pytest é que ele permite que você escreva funções de teste. Semelhante aos arquivos de teste, as funções de teste devem ser prefixadas com test_
. O test_
prefixo garante que Pytest coleta o teste e o executa.
Veja como é uma função de teste simples:
def test_main():
assert "a string value" == "a string value"
Nota
Se você está familiarizado com unittest
, pode ser surpreendente ver o uso de na função de assert
teste. Abordaremos afirmações simples com mais detalhes mais tarde, mas com o Pytest, você obtém relatórios de falhas ricos com afirmações simples.
Classes de ensaio e métodos de ensaio
Semelhante às convenções para arquivos e funções, classes de teste e métodos de teste usam as seguintes convenções:
- As classes de teste são prefixadas com
Test
- Os métodos de teste são prefixados com
test_
Uma diferença central com a biblioteca do unittest
Python é que não há necessidade de herança.
O exemplo a seguir usa esses prefixos e outras convenções de nomenclatura Python para classes e métodos. Ele demonstra uma pequena classe de teste que está verificando nomes de usuário em um aplicativo.
class TestUser:
def test_username(self):
assert default() == "default username"
Executar testes
Pytest é uma estrutura de teste e um corredor de teste. O executor de teste é um executável na linha de comando que, em um alto nível, pode:
- Execute a coleção de testes localizando todos os arquivos de teste, classes de teste e funções de teste para uma execução de teste.
- Inicie uma execução de teste executando todos os testes.
- Acompanhe falhas, erros e aprovação em testes.
- Forneça relatórios detalhados no final de uma execução de teste.
Nota
Como o Pytest é uma biblioteca externa, ele deve ser instalado para poder usá-lo.
Dado esse conteúdo em um arquivo test_main.py , podemos ver como o Pytest se comporta executando os testes:
# contents of test_main.py file
def test_main():
assert True
Na linha de comando, no mesmo caminho onde o arquivo test_main.py existe, podemos executar o pytest
executável:
$ pytest
=========================== test session starts ============================
platform -- Python 3.10.1, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private/tmp/project
collected 1 item
test_main.py . [100%]
============================ 1 passed in 0.00s =============================
Nos bastidores, o Pytest coleta o teste de exemplo no arquivo de teste sem qualquer configuração necessária.
A poderosa afirmação
Até agora, nossos exemplos de teste estão todos usando a chamada simples assert
. Normalmente, em Python, a assert
instrução não é usada para testes porque carece de relatórios adequados quando a asserção falha. Pytest, no entanto, não tem essa limitação. Nos bastidores, o Pytest está permitindo que a instrução realize comparações ricas sem forçar o usuário a escrever mais código ou configurar nada.
Usando a instrução plain assert
, você pode fazer uso dos operadores do Python. Por exemplo: >
, <
, !=
, >=
, ou <=
. Todos os operadores do Python são válidos. Esse recurso pode ser o recurso mais crucial do Pytest: você não precisa aprender uma nova sintaxe para escrever asserções.
Vamos ver como isso se traduz ao lidar com comparações comuns com objetos Python. Neste caso, vamos analisar o relatório de falhas ao comparar cadeias de caracteres longas:
================================= FAILURES =================================
____________________________ test_long_strings _____________________________
def test_long_strings():
left = "this is a very long strings to be compared with another long string"
right = "This is a very long string to be compared with another long string"
> assert left == right
E AssertionError: assert 'this is a ve...r long string' == 'This is a ve...r long string'
E - This is a very long string to be compared with another long string
E ? ^
E + this is a very long strings to be compared with another long string
E ? ^ +
test_main.py:4: AssertionError
Pytest mostra contexto útil em torno da falha. Um invólucro incorreto no início da cadeia de caracteres e um caractere extra em uma palavra. Mas além das strings, o Pytest pode ajudar com outros objetos e estruturas de dados. Por exemplo, veja como ele se comporta com listas:
________________________________ test_lists ________________________________
def test_lists():
left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
right = ["sugar", "coffee", "wheat", "salt", "water", "milk"]
> assert left == right
E AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'co...ater', 'milk']
E At index 1 diff: 'wheat' != 'coffee'
E Full diff:
E - ['sugar', 'coffee', 'wheat', 'salt', 'water', 'milk']
E ? ---------
E + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E ? +++++++++
test_main.py:9: AssertionError
Este relatório identifica que o índice 1 (segundo item da lista) é diferente. Não só identifica o número do índice, como também fornece uma representação da falha. Além de comparações de itens, ele também pode relatar se os itens estão faltando e fornecer informações que podem dizer exatamente qual item pode ser. No seguinte caso, isto é "milk"
:
________________________________ test_lists ________________________________
def test_lists():
left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
right = ["sugar", "wheat", "salt", "water", "milk"]
> assert left == right
E AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'wh...ater', 'milk']
E At index 2 diff: 'coffee' != 'salt'
E Left contains one more item: 'milk'
E Full diff:
E - ['sugar', 'wheat', 'salt', 'water', 'milk']
E + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E ? ++++++++++
test_main.py:9: AssertionError
Por fim, vejamos como se comporta com os dicionários. Comparar dois grandes dicionários pode ser esmagador se houver falhas, mas Pytest faz um excelente trabalho em fornecer contexto e identificar a falha:
____________________________ test_dictionaries _____________________________
def test_dictionaries():
left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
> assert left == right
E AssertionError: assert {'county': 'F...rry Ln.', ...} == {'county': 'F...ry Lane', ...}
E Omitting 3 identical items, use -vv to show
E Differing items:
E {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E {'number': 39} != {'number': 38}
E Full diff:
E {
E 'county': 'Frett',...
E
E ...Full output truncated (12 lines hidden), use '-vv' to show
Neste teste, há duas falhas no dicionário. Uma é que o "street"
valor é diferente, e a outra é que "number"
não corresponde.
O Pytest está detetando com precisão essas diferenças (mesmo que seja uma falha em um único teste). Como os dicionários contêm muitos itens, Pytest omite as partes idênticas e mostra apenas conteúdo relevante. Vamos ver o que acontece se fizermos uso da bandeira sugerida -vv
para aumentar a verbosidade na saída:
____________________________ test_dictionaries _____________________________
def test_dictionaries():
left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
> assert left == right
E AssertionError: assert {'county': 'Frett',\n 'number': 39,\n 'state': 'Nevada',\n 'street': 'Ferry Ln.',\n 'zipcode': 30877} == {'county': 'Frett',\n 'number': 38,\n 'state': 'Nevada',\n 'street': 'Ferry Lane',\n 'zipcode': 30877}
E Common items:
E {'county': 'Frett', 'state': 'Nevada', 'zipcode': 30877}
E Differing items:
E {'number': 39} != {'number': 38}
E {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E Full diff:
E {
E 'county': 'Frett',
E - 'number': 38,
E ? ^
E + 'number': 39,
E ? ^
E 'state': 'Nevada',
E - 'street': 'Ferry Lane',
E ? - ^
E + 'street': 'Ferry Ln.',
E ? ^
E 'zipcode': 30877,
E }
Ao executar pytest -vv
o , o relatório aumenta a quantidade de detalhes e fornece uma comparação granular. Este relatório não apenas deteta e mostra a falha, mas também permite que você faça alterações rapidamente para corrigir o problema.