Pytest - Guia rápido

Pytest é uma estrutura de teste baseada em Python, que é usada para escrever e executar códigos de teste. Nos dias atuais de serviços REST, pytest é usado principalmente para teste de API, embora possamos usar pytest para escrever testes simples a complexos, ou seja, podemos escrever códigos para testar API, banco de dados, IU, etc.

Vantagens do Pytest

As vantagens do Pytest são as seguintes -

  • O Pytest pode executar vários testes em paralelo, o que reduz o tempo de execução do conjunto de testes.

  • O Pytest tem sua própria maneira de detectar o arquivo de teste e as funções de teste automaticamente, se não for mencionado explicitamente.

  • O Pytest nos permite pular um subconjunto dos testes durante a execução.

  • O Pytest nos permite executar um subconjunto de todo o conjunto de testes.

  • Pytest é gratuito e de código aberto.

  • Por causa de sua sintaxe simples, pytest é muito fácil de começar.

Neste tutorial, explicaremos os fundamentos do pytest com programas de amostra.

Neste capítulo, aprenderemos como instalar o pytest.

Para iniciar a instalação, execute o seguinte comando -

pip install pytest == 2.9.1

Podemos instalar qualquer versão do pytest. Aqui, 2.9.1 é a versão que estamos instalando.

Para instalar a versão mais recente do pytest, execute o seguinte comando -

pip install pytest

Confirme a instalação usando o seguinte comando para exibir a seção de ajuda do pytest.

pytest -h

Executar pytest sem mencionar um nome de arquivo irá executar todos os arquivos de formato test_*.py ou *_test.pyno diretório e subdiretórios atuais. O Pytest identifica automaticamente esses arquivos como arquivos de teste. Nóscan faça pytest executar outros nomes de arquivo, mencionando-os explicitamente.

Pytest requer que os nomes das funções de teste comecem com test. Nomes de funções que não têm formatotest*não são consideradas funções de teste por pytest. Nóscannot faça pytest explicitamente considerar qualquer função que não comece com test como uma função de teste.

Vamos entender a execução de testes em nossos capítulos subsequentes.

Agora, começaremos com nosso primeiro programa de teste. Primeiro criaremos um diretório e, assim, criaremos nossos arquivos de teste no diretório.

Vamos seguir as etapas abaixo -

  • Crie um novo diretório chamado automation e navegue até o diretório em sua linha de comando.

  • Crie um arquivo chamado test_square.py e adicione o código abaixo a esse arquivo.

import math

def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

def testsquare():
   num = 7
   assert 7*7 == 40

def tesequality():
   assert 10 == 11

Execute o teste usando o seguinte comando -

pytest

O comando acima irá gerar a seguinte saída -

test_square.py .F
============================================== FAILURES 
==============================================
______________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num=7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.06 seconds 
=================================

Veja a primeira linha do resultado. Ele exibe o nome do arquivo e os resultados. F representa uma falha de teste e o ponto (.) Representa um sucesso de teste.

Abaixo disso, podemos ver os detalhes dos testes que falharam. Ele mostrará em qual declaração o teste falhou. Em nosso exemplo, 7 * 7 é comparado para igualdade contra 40, o que está errado. No final, podemos ver o resumo da execução do teste, 1 falha e 1 aprovada.

A função tesequality não é executada porque o pytest não irá considerá-la como um teste visto que o seu nome não é do formato test*.

Agora, execute o comando abaixo e veja o resultado novamente -

pytest -v

-v aumenta a verbosidade.

test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
============================================== FAILURES 
==============================================
_____________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.04 seconds 
=================================

Agora o resultado é mais explicativo sobre o teste que falhou e o teste que passou.

Note - o comando pytest irá executar todos os arquivos de formato test_* ou *_test no diretório e subdiretórios atuais.

Neste capítulo, aprenderemos como executar um único arquivo de teste e vários arquivos de teste. Já temos um arquivo de testetest_square.pycriada. Crie um novo arquivo de testetest_compare.py com o seguinte código -

def test_greater():
   num = 100
   assert num > 100

def test_greater_equal():
   num = 100
   assert num >= 100

def test_less():
   num = 100
   assert num < 200

Agora, para executar todos os testes de todos os arquivos (2 arquivos aqui), precisamos executar o seguinte comando -

pytest -v

O comando acima irá executar testes de ambos test_square.py e test_compare.py. A saída será gerada da seguinte forma -

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
================================================ FAILURES 
================================================
______________________________________________ test_greater 
______________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100

test_compare.py:3: AssertionError
_______________________________________________ testsquare 
_______________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40

test_square.py:9: AssertionError
=================================== 2 failed, 3 passed in 0.07 seconds 
===================================

Para executar os testes de um arquivo específico, use a seguinte sintaxe -

pytest <filename> -v

Agora, execute o seguinte comando -

pytest test_compare.py -v

O comando acima irá executar os testes apenas a partir do arquivo test_compare.py. Nosso resultado será -

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
================================= 1 failed, 2 passed in 0.04 seconds 
=================================

Em um cenário real, teremos vários arquivos de teste e cada arquivo terá uma série de testes. Os testes cobrirão vários módulos e funcionalidades. Suponha que desejamos executar apenas um conjunto específico de testes; como vamos fazer isso?

O Pytest fornece duas maneiras de executar o subconjunto do conjunto de testes.

  • Selecione os testes a serem executados com base na correspondência de substring de nomes de teste.
  • Selecione grupos de testes a serem executados com base nos marcadores aplicados.

Explicaremos esses dois com exemplos nos capítulos subsequentes.

Para executar os testes que contêm uma string em seu nome, podemos usar a seguinte sintaxe -

pytest -k <substring> -v

-k <substring> representa a substring a ser pesquisada nos nomes de teste.

Agora, execute o seguinte comando -

pytest -k great -v

Isso executará todos os nomes de teste com a palavra ‘great’em seu nome. Neste caso, eles sãotest_greater() e test_greater_equal(). Veja o resultado abaixo.

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
def test_greater():
num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
========================== 1 failed, 1 passed, 3 deselected in 0.07 seconds 
==========================

Aqui no resultado, podemos ver 3 testes desmarcados. Isso ocorre porque esses nomes de teste não contêm a palavragreat neles.

Note - O nome da função de teste ainda deve começar com 'teste'.

Neste capítulo, aprenderemos como agrupar os testes usando marcadores.

Pytest nos permite usar marcadores em funções de teste. Os marcadores são usados ​​para definir vários recursos / atributos para funções de teste. Pytest fornece muitos marcadores embutidos, como xfail, skip e parametrize. Além disso, os usuários podem criar seus próprios nomes de marcadores. Os marcadores são aplicados nos testes usando a sintaxe fornecida abaixo -

@pytest.mark.<markername>

Para usar marcadores, temos que import pytestmódulo no arquivo de teste. Podemos definir nossos próprios nomes de marcadores para os testes e executar os testes com esses nomes de marcadores.

Para executar os testes marcados, podemos usar a seguinte sintaxe -

pytest -m <markername> -v

-m <markername> representa o nome do marcador dos testes a serem executados.

Atualize nossos arquivos de teste test_compare.py e test_square.pycom o seguinte código. Estamos definindo 3 marcadores– great, square, others.

test_compare.py

import pytest
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

test_square.py

import pytest
import math

@pytest.mark.square
def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

@pytest.mark.square
def testsquare():
   num = 7
   assert 7*7 == 40

@pytest.mark.others
   def test_equality():
   assert 10 == 11

Agora, para executar os testes marcados como others, execute o seguinte comando -

pytest -m others -v

Veja o resultado abaixo. Ele executou os 2 testes marcados comoothers.

test_compare.py::test_less PASSED
test_square.py::test_equality FAILED
============================================== FAILURES
==============================================
___________________________________________ test_equality
____________________________________________
   @pytest.mark.others
   def test_equality():
>  assert 10 == 11
E  assert 10 == 11
test_square.py:16: AssertionError
========================== 1 failed, 1 passed, 4 deselected in 0.08 seconds
==========================

Da mesma forma, podemos executar testes com outros marcadores também - ótimo, compare

Luminárias são funções que serão executadas antes de cada função de teste à qual são aplicadas. As luminárias são usadas para alimentar alguns dados para os testes, como conexões de banco de dados, URLs para testar e algum tipo de dado de entrada. Portanto, em vez de executar o mesmo código para todos os testes, podemos anexar a função de fixação aos testes e ela será executada e retornará os dados para o teste antes de executar cada teste.

Uma função é marcada como um acessório por -

@pytest.fixture

Uma função de teste pode usar um aparelho mencionando o nome do aparelho como um parâmetro de entrada.

Crie um arquivo test_div_by_3_6.py e adicione o código abaixo a ele

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Aqui, temos uma função de fixação chamada input_value, que fornece a entrada para os testes. Para acessar a função do aparelho, os testes devem mencionar o nome do aparelho como parâmetro de entrada.

Pytest, enquanto o teste está sendo executado, verá o nome do aparelho como parâmetro de entrada. Em seguida, ele executa a função de fixação e o valor retornado é armazenado no parâmetro de entrada, que pode ser usado pelo teste.

Execute o teste usando o seguinte comando -

pytest -k divisible -v

O comando acima irá gerar o seguinte resultado -

test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:12: AssertionError
========================== 1 failed, 1 passed, 6 deselected in 0.07 seconds
==========================

No entanto, a abordagem vem com sua própria limitação. Uma função de fixação definida dentro de um arquivo de teste tem um escopo apenas dentro do arquivo de teste. Não podemos usar esse acessório em outro arquivo de teste. Para disponibilizar um fixture para vários arquivos de teste, temos que definir a função do fixture em um arquivo chamado conftest.py.conftest.py é explicado no próximo capítulo.

Podemos definir as funções de fixação neste arquivo para torná-las acessíveis em vários arquivos de teste.

Crie um novo arquivo conftest.py e adicione o código abaixo nele -

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

Edite o test_div_by_3_6.py para remover a função de fixação -

import pytest

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Crie um novo arquivo test_div_by_13.py -

import pytest

def test_divisible_by_13(input_value):
   assert input_value % 13 == 0

Agora, temos os arquivos test_div_by_3_6.py e test_div_by_13.py fazendo uso do acessório definido em conftest.py.

Execute os testes executando o seguinte comando -

pytest -k divisible -v

O comando acima irá gerar o seguinte resultado -

test_div_by_13.py::test_divisible_by_13 PASSED
test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:7: AssertionError
========================== 1 failed, 2 passed, 6 deselected in 0.09 seconds
==========================

Os testes procurarão fixture no mesmo arquivo. Como o fixture não foi encontrado no arquivo, ele irá verificar o fixture no arquivo conftest.py. Ao encontrá-lo, o método fixture é invocado e o resultado é retornado para o argumento de entrada do teste.

A parametrização de um teste é feita para executar o teste em vários conjuntos de entradas. Podemos fazer isso usando o seguinte marcador -

@pytest.mark.parametrize

Copie o código abaixo em um arquivo chamado test_multiplication.py -

import pytest

@pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
def test_multiplication_11(num, output):
   assert 11*num == output

Aqui, o teste multiplica uma entrada por 11 e compara o resultado com a saída esperada. O teste possui 4 conjuntos de entradas, cada uma com 2 valores - um é o número a ser multiplicado por 11 e o outro é o resultado esperado.

Execute o teste executando o seguinte comando -

Pytest -k multiplication -v

O comando acima irá gerar a seguinte saída -

test_multiplication.py::test_multiplication_11[1-11] PASSED
test_multiplication.py::test_multiplication_11[2-22] PASSED
test_multiplication.py::test_multiplication_11[3-35] FAILED
test_multiplication.py::test_multiplication_11[4-44] PASSED
============================================== FAILURES
==============================================
_________________ test_multiplication_11[3-35] __________________
num = 3, output = 35
   @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
   def test_multiplication_11(num, output):
>  assert 11*num == output
E  assert (11 * 3) == 35
test_multiplication.py:5: AssertionError
============================== 1 failed, 3 passed, 8 deselected in 0.08 seconds
==============================

Neste capítulo, aprenderemos sobre os testes Skip e Xfail no Pytest.

Agora, considere as situações abaixo -

  • Um teste não é relevante por algum tempo devido a alguns motivos.
  • Um novo recurso está sendo implementado e já adicionamos um teste para esse recurso.

Nessas situações, temos a opção de xfail no teste ou pular os testes.

O Pytest executará o teste xfailed, mas não será considerado como parte dos testes reprovados ou aprovados. Os detalhes desses testes não serão impressos mesmo se o teste falhar (lembre-se de que o pytest geralmente imprime os detalhes do teste que falhou). Podemos xfail nos testes usando o seguinte marcador -

@pytest.mark.xfail

Ignorar um teste significa que o teste não será executado. Podemos pular os testes usando o seguinte marcador -

@pytest.mark.skip

Mais tarde, quando o teste se tornar relevante, podemos remover os marcadores.

Edite o test_compare.py já temos que incluir os marcadores xfail e skip -

import pytest
@pytest.mark.xfail
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.xfail
@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.skip
@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

Execute o teste usando o seguinte comando -

pytest test_compare.py -v

Após a execução, o comando acima irá gerar o seguinte resultado -

test_compare.py::test_greater xfail
test_compare.py::test_greater_equal XPASS
test_compare.py::test_less SKIPPED
============================ 1 skipped, 1 xfailed, 1 xpassed in 0.06 seconds
============================

Em um cenário real, uma vez que uma nova versão do código está pronta para ser implantada, ela é primeiro implantada no ambiente de pré-produção / preparação. Em seguida, um conjunto de testes é executado nele.

O código é qualificado para implantação na produção apenas se o conjunto de testes for aprovado. Se houver falha no teste, seja um ou vários, o código não está pronto para produção.

Portanto, e se quisermos interromper a execução do conjunto de testes logo após n número de falhas de teste. Isso pode ser feito em pytest usando maxfail.

A sintaxe para interromper a execução do conjunto de testes logo após n número de falhas de teste é a seguinte -

pytest --maxfail = <num>

Crie um arquivo test_failure.py com o código a seguir.

import pytest
import math

def test_sqrt_failure():
   num = 25
   assert math.sqrt(num) == 6

def test_square_failure():
   num = 7
   assert 7*7 == 40

def test_equality_failure():
   assert 10 == 11

Todos os 3 testes falharão ao executar este arquivo de teste. Aqui, vamos parar a execução do teste após uma falha por -

pytest test_failure.py -v --maxfail = 1
test_failure.py::test_sqrt_failure FAILED
=================================== FAILURES
=================================== _______________________________________
test_sqrt_failure __________________________________________
   def test_sqrt_failure():
   num = 25
>  assert math.sqrt(num) == 6
E  assert 5.0 == 6
E  + where 5.0 = <built-in function sqrt>(25)
E  + where <built-in function sqrt>= math.sqrt
test_failure.py:6: AssertionError
=============================== 1 failed in 0.04 seconds
===============================

No resultado acima, podemos ver que a execução foi interrompida em uma falha.

Por padrão, o pytest executa testes em ordem sequencial. Em um cenário real, um conjunto de testes terá vários arquivos de teste e cada arquivo terá vários testes. Isso levará a um grande tempo de execução. Para superar isso, o pytest nos fornece a opção de executar testes em paralelo.

Para isso, precisamos primeiro instalar o plugin pytest-xdist.

Instale o pytest-xdist executando o seguinte comando -

pip install pytest-xdist

Agora, podemos executar testes usando a sintaxe pytest -n <num>

pytest -n 3

-n <num> executa os testes usando vários trabalhadores, aqui estão 3.

Não teremos muita diferença de tempo quando houver apenas alguns testes para executar. No entanto, é importante quando o conjunto de testes é grande.

Podemos gerar os detalhes da execução do teste em um arquivo xml. Este arquivo xml é útil principalmente nos casos em que temos um painel que projeta os resultados do teste. Nesses casos, o xml pode ser analisado para obter os detalhes da execução.

Vamos agora executar os testes de test_multiplcation.py e gerar o xml executando

pytest test_multiplication.py -v --junitxml="result.xml"

Agora podemos ver result.xml é gerado com os seguintes dados -

<?xml version = "1.0" encoding = "utf-8"?>
<testsuite errors = "0" failures = "1"
name = "pytest" skips = "0" tests = "4" time = "0.061">
   <testcase classname = "test_multiplication"          
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[1-11]"
      time = "0.00117516517639>
   </testcase>
   
   <testcase classname = "test_multiplication"    
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[2-22]"
      time = "0.00155973434448">
   </testcase>

   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[3-35]" time = "0.00144290924072">
      failure message = "assert (11 * 3) == 35">num = 3, output = 35

         @pytest.mark.parametrize("num,
         output",[(1,11),(2,22),(3,35),(4,44)])
            
         def test_multiplication_11(num, output):> 
         assert 11*num == output
         E assert (11 * 3) == 35

         test_multiplication.py:5: AssertionErro
      </failure>
   </testcase>
   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[4-44]"
      time = "0.000945091247559">
   </testcase>
</testsuite>

Aqui, a tag <testsuit> resume que houve 4 testes e o número de falhas é 1.

  • A etiqueta <testcase> fornece os detalhes de cada teste executado.

  • A tag <failure> fornece os detalhes do código de teste com falha.

Neste tutorial pytest, cobrimos as seguintes áreas -

  • Instalando o pytest ..
  • Identificar arquivos de teste e funções de teste.
  • Executando todos os arquivos de teste usando pytest –v.
  • Executando arquivo específico usando pytest <nome do arquivo> -v.
  • Execute testes pela substring correspondente a pytest -k <substring> -v.
  • Execute testes com base nos marcadores pytest -m <marker_name> -v.
  • Criação de luminárias usando @ pytest.fixture.
  • conftest.py permite acessar equipamentos de vários arquivos.
  • Parametrizando testes usando @ pytest.mark.parametrize.
  • O Xfailing testa usando @ pytest.mark.xfail.
  • Ignorando testes usando @ pytest.mark.skip.
  • Pare a execução do teste em n falhas usando pytest --maxfail = <num>.
  • Execução de testes em paralelo usando pytest -n <num>.
  • Gerando resultados xml usando pytest -v --junitxml = "result.xml".

Este tutorial apresentou a estrutura pytest. Agora você deve ser capaz de começar a escrever testes usando o pytest.

Como uma boa prática -

  • Crie diferentes arquivos de teste com base na funcionalidade / módulo sendo testado.
  • Dê nomes significativos aos arquivos e métodos de teste.
  • Tenha marcadores suficientes para agrupar os testes com base em vários critérios.
  • Use acessórios sempre que necessário.