Pytest - Guía rápida

Pytest es un marco de prueba basado en Python, que se utiliza para escribir y ejecutar códigos de prueba. En la actualidad de los servicios REST, pytest se usa principalmente para pruebas de API, aunque podemos usar pytest para escribir pruebas simples a complejas, es decir, podemos escribir códigos para probar API, bases de datos, UI, etc.

Ventajas de Pytest

Las ventajas de Pytest son las siguientes:

  • Pytest puede ejecutar múltiples pruebas en paralelo, lo que reduce el tiempo de ejecución del conjunto de pruebas.

  • Pytest tiene su propia forma de detectar el archivo de prueba y las funciones de prueba automáticamente, si no se menciona explícitamente.

  • Pytest nos permite omitir un subconjunto de las pruebas durante la ejecución.

  • Pytest nos permite ejecutar un subconjunto de todo el conjunto de pruebas.

  • Pytest es gratuito y de código abierto.

  • Debido a su sintaxis simple, pytest es muy fácil de comenzar.

En este tutorial, explicaremos los fundamentos de Pytest con programas de muestra.

En este capítulo, aprenderemos cómo instalar pytest.

Para iniciar la instalación, ejecute el siguiente comando:

pip install pytest == 2.9.1

Podemos instalar cualquier versión de pytest. Aquí, 2.9.1 es la versión que estamos instalando.

Para instalar la última versión de pytest, ejecute el siguiente comando:

pip install pytest

Confirme la instalación usando el siguiente comando para mostrar la sección de ayuda de pytest.

pytest -h

Ejecutar pytest sin mencionar un nombre de archivo ejecutará todos los archivos de formato test_*.py o *_test.pyen el directorio y subdirectorios actuales. Pytest identifica automáticamente esos archivos como archivos de prueba. Nosotroscan Haga que pytest ejecute otros nombres de archivo mencionándolos explícitamente.

Pytest requiere que los nombres de las funciones de prueba comiencen con test. Nombres de funciones que no tienen formatotest*pytest no las considera funciones de prueba. Nosotroscannot explícitamente hacer que pytest considere cualquier función que no comience con test como función de prueba.

Comprenderemos la ejecución de las pruebas en los capítulos siguientes.

Ahora, comenzaremos con nuestro primer programa Pytest. Primero crearemos un directorio y, por lo tanto, crearemos nuestros archivos de prueba en el directorio.

Sigamos los pasos que se muestran a continuación:

  • Crea un nuevo directorio llamado automation y navegue al directorio en su línea de comando.

  • Crea un archivo llamado test_square.py y agregue el siguiente código a ese archivo.

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

Ejecute la prueba con el siguiente comando:

pytest

El comando anterior generará la siguiente salida:

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 
=================================

Vea la primera línea del resultado. Muestra el nombre del archivo y los resultados. F representa un error de prueba y el punto (.) Representa un éxito de prueba.

Debajo de eso, podemos ver los detalles de las pruebas fallidas. Mostrará en qué sentencia falló la prueba. En nuestro ejemplo, se compara 7 * 7 para la igualdad con 40, lo cual es incorrecto. Al final, podemos ver el resumen de ejecución de la prueba, 1 falló y 1 pasó.

La función tesequality no se ejecuta porque pytest no la considerará como una prueba ya que su nombre no es del formato test*.

Ahora, ejecute el siguiente comando y vea el resultado nuevamente:

pytest -v

-v aumenta la verbosidad.

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 
=================================

Ahora el resultado es más explicativo sobre la prueba que falló y la prueba que pasó.

Note - el comando pytest ejecutará todos los archivos de formato test_* o *_test en el directorio y subdirectorios actuales.

En este capítulo, aprenderemos cómo ejecutar un solo archivo de prueba y varios archivos de prueba. Ya tenemos un archivo de pruebatest_square.pycreado. Crea un nuevo archivo de pruebatest_compare.py con el siguiente 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

Ahora, para ejecutar todas las pruebas de todos los archivos (2 archivos aquí), necesitamos ejecutar el siguiente comando:

pytest -v

El comando anterior ejecutará pruebas de ambos test_square.py y test_compare.py. La salida se generará de la siguiente manera:

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 ejecutar las pruebas desde un archivo específico, use la siguiente sintaxis:

pytest <filename> -v

Ahora, ejecute el siguiente comando:

pytest test_compare.py -v

El comando anterior ejecutará las pruebas solo desde el archivo test_compare.py. Nuestro 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 
=================================

En un escenario real, tendremos varios archivos de prueba y cada archivo tendrá una serie de pruebas. Las pruebas cubrirán varios módulos y funcionalidades. Supongamos que queremos ejecutar solo un conjunto específico de pruebas; ¿Cómo lo hacemos?

Pytest proporciona dos formas de ejecutar el subconjunto del conjunto de pruebas.

  • Seleccione las pruebas para ejecutar según la coincidencia de subcadenas de los nombres de las pruebas.
  • Seleccione grupos de pruebas para ejecutar en función de los marcadores aplicados.

Explicaremos estos dos con ejemplos en los capítulos siguientes.

Para ejecutar las pruebas que contienen una cadena en su nombre podemos usar la siguiente sintaxis:

pytest -k <substring> -v

-k <substring> representa la subcadena que se buscará en los nombres de las pruebas.

Ahora, ejecute el siguiente comando:

pytest -k great -v

Esto ejecutará todos los nombres de prueba que tengan la palabra ‘great’en su nombre. En este caso, sontest_greater() y test_greater_equal(). Vea el resultado a continuación.

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 
==========================

Aquí, en el resultado, podemos ver 3 pruebas deseleccionadas. Esto se debe a que esos nombres de prueba no contienen la palabragreat en ellos.

Note - El nombre de la función de prueba debe comenzar con 'prueba'.

En este capítulo, aprenderemos cómo agrupar las pruebas usando marcadores.

Pytest nos permite usar marcadores en funciones de prueba. Los marcadores se utilizan para establecer varias características / atributos para probar funciones. Pytest proporciona muchos marcadores incorporados como xfail, skip y parametrize. Aparte de eso, los usuarios pueden crear sus propios nombres de marcadores. Los marcadores se aplican en las pruebas utilizando la sintaxis que se indica a continuación:

@pytest.mark.<markername>

Para usar marcadores, tenemos que import pytestmódulo en el archivo de prueba. Podemos definir nuestros propios nombres de marcadores para las pruebas y ejecutar las pruebas que tengan esos nombres de marcadores.

Para ejecutar las pruebas marcadas, podemos usar la siguiente sintaxis:

pytest -m <markername> -v

-m <nombre de marcador> representa el nombre de marcador de las pruebas que se ejecutarán.

Actualice nuestros archivos de prueba test_compare.py y test_square.pycon el siguiente código. Estamos definiendo 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

Ahora para ejecutar las pruebas marcadas como others, ejecute el siguiente comando:

pytest -m others -v

Vea el resultado a continuación. Ejecutó las 2 pruebas marcadas 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
==========================

Del mismo modo, también podemos ejecutar pruebas con otros marcadores; genial, compare

Los accesorios son funciones que se ejecutarán antes de cada función de prueba a la que se aplique. Los accesorios se utilizan para alimentar algunos datos a las pruebas, como conexiones de bases de datos, URL para probar y algún tipo de datos de entrada. Por lo tanto, en lugar de ejecutar el mismo código para cada prueba, podemos adjuntar la función de fijación a las pruebas y se ejecutará y devolverá los datos a la prueba antes de ejecutar cada prueba.

Una función está marcada como accesorio por:

@pytest.fixture

Una función de prueba puede utilizar un dispositivo mencionando el nombre del dispositivo como parámetro de entrada.

Crea un archivo test_div_by_3_6.py y agregue el siguiente código

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

Aquí, tenemos una función de accesorio llamada input_value, que proporciona la entrada a las pruebas. Para acceder a la función del dispositivo, las pruebas deben mencionar el nombre del dispositivo como parámetro de entrada.

Pytest mientras se ejecuta la prueba, verá el nombre del dispositivo como parámetro de entrada. Luego ejecuta la función de fijación y el valor devuelto se almacena en el parámetro de entrada, que puede ser utilizado por la prueba.

Ejecute la prueba usando el siguiente comando:

pytest -k divisible -v

El comando anterior generará el siguiente 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
==========================

Sin embargo, el enfoque tiene sus propias limitaciones. Una función de dispositivo definida dentro de un archivo de prueba tiene un alcance solo dentro del archivo de prueba. No podemos usar ese dispositivo en otro archivo de prueba. Para que un dispositivo esté disponible para varios archivos de prueba, tenemos que definir la función del dispositivo en un archivo llamado conftest.py.conftest.py se explica en el siguiente capítulo.

Podemos definir las funciones del dispositivo en este archivo para que sean accesibles a través de múltiples archivos de prueba.

Crea un archivo nuevo conftest.py y agregue el siguiente código en él:

import pytest

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

Edite el test_div_by_3_6.py para eliminar la función de luminaria -

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

Crea un archivo nuevo test_div_by_13.py -

import pytest

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

Ahora tenemos los archivos test_div_by_3_6.py y test_div_by_13.py haciendo uso de la luminaria definida en conftest.py.

Ejecute las pruebas ejecutando el siguiente comando:

pytest -k divisible -v

El comando anterior generará el siguiente 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
==========================

Las pruebas buscarán el accesorio en el mismo archivo. Como el dispositivo no se encuentra en el archivo, buscará el dispositivo en el archivo conftest.py. Al encontrarlo, se invoca el método fixture y el resultado se devuelve al argumento de entrada de la prueba.

La parametrización de una prueba se realiza para ejecutar la prueba con varios conjuntos de entradas. Podemos hacer esto usando el siguiente marcador:

@pytest.mark.parametrize

Copie el siguiente código en un archivo llamado 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

Aquí, la prueba multiplica una entrada por 11 y compara el resultado con la salida esperada. La prueba tiene 4 conjuntos de entradas, cada uno tiene 2 valores: uno es el número que se debe multiplicar por 11 y el otro es el resultado esperado.

Ejecute la prueba ejecutando el siguiente comando:

Pytest -k multiplication -v

El comando anterior generará la siguiente salida:

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
==============================

En este capítulo, aprenderemos sobre las pruebas Skip y Xfail en Pytest.

Ahora, considere las siguientes situaciones:

  • Una prueba no es relevante durante algún tiempo debido a algunas razones.
  • Se está implementando una nueva característica y ya agregamos una prueba para esa característica.

En estas situaciones, tenemos la opción de xfallar la prueba u omitir las pruebas.

Pytest ejecutará la prueba xfailed, pero no se considerará como pruebas parcialmente fallidas o aprobadas. Los detalles de estas pruebas no se imprimirán incluso si la prueba falla (recuerde que pytest generalmente imprime los detalles de la prueba fallida). Podemos xfail las pruebas usando el siguiente marcador:

@pytest.mark.xfail

Saltarse una prueba significa que la prueba no se ejecutará. Podemos omitir las pruebas usando el siguiente marcador:

@pytest.mark.skip

Posteriormente, cuando la prueba sea relevante, podemos eliminar los marcadores.

Edite el test_compare.py ya tenemos que incluir los marcadores xfail y 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

Ejecute la prueba usando el siguiente comando:

pytest test_compare.py -v

Tras la ejecución, el comando anterior generará el siguiente 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
============================

En un escenario real, una vez que una nueva versión del código está lista para implementarse, primero se implementa en un entorno de preproducción / ensayo. Luego, se ejecuta un conjunto de pruebas.

El código está calificado para su implementación en producción solo si pasa el conjunto de pruebas. Si hay una falla en la prueba, ya sea una o muchas, el código no está listo para producción.

Por lo tanto, ¿qué sucede si queremos detener la ejecución de la suite de pruebas poco después de que n número de pruebas fallan? Esto se puede hacer en pytest usando maxfail.

La sintaxis para detener la ejecución de la suite de pruebas poco después de que n número de pruebas fallan es la siguiente:

pytest --maxfail = <num>

Cree un archivo test_failure.py con el siguiente código.

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

Las 3 pruebas fallarán al ejecutar este archivo de prueba. Aquí, vamos a detener la ejecución de la prueba después de una falla en sí misma:

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
===============================

En el resultado anterior, podemos ver que la ejecución se detiene en una falla.

Por defecto, pytest ejecuta las pruebas en orden secuencial. En un escenario real, un conjunto de pruebas tendrá varios archivos de prueba y cada archivo tendrá un montón de pruebas. Esto dará lugar a un gran tiempo de ejecución. Para superar esto, pytest nos brinda una opción para ejecutar pruebas en paralelo.

Para esto, primero debemos instalar el complemento pytest-xdist.

Instale pytest-xdist ejecutando el siguiente comando:

pip install pytest-xdist

Ahora, podemos ejecutar pruebas usando la sintaxis pytest -n <num>

pytest -n 3

-n <num> ejecuta las pruebas utilizando varios trabajadores, aquí es 3.

No tendremos mucha diferencia de tiempo cuando solo haya unas pocas pruebas para ejecutar. Sin embargo, importa cuando el conjunto de pruebas es grande.

Podemos generar los detalles de la ejecución de la prueba en un archivo xml. Este archivo xml es principalmente útil en los casos en que tenemos un tablero que proyecta los resultados de la prueba. En tales casos, el xml se puede analizar para obtener los detalles de la ejecución.

Ahora ejecutaremos las pruebas de test_multiplcation.py y generaremos el xml ejecutando

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

Ahora podemos ver que result.xml se genera con los siguientes datos:

<?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>

Aquí, la etiqueta <testsuit> resume que hubo 4 pruebas y el número de fallas es 1.

  • La etiqueta <testcase> da los detalles de cada prueba ejecutada.

  • La etiqueta <failure> proporciona los detalles del código de prueba fallido.

En este tutorial de Pytest, cubrimos las siguientes áreas:

  • Instalando pytest ..
  • Identificación de archivos de prueba y funciones de prueba.
  • Ejecutando todos los archivos de prueba usando pytest –v.
  • Ejecutando un archivo específico usando pytest <nombre de archivo> -v.
  • Ejecute pruebas con una subcadena que coincida con pytest -k <substring> -v.
  • Ejecute pruebas basadas en marcadores pytest -m <nombre_marker> -v.
  • Creando accesorios usando @ pytest.fixture.
  • conftest.py permite acceder a dispositivos desde varios archivos.
  • Parametrizar pruebas usando @ pytest.mark.parametrize.
  • Xfailing pruebas usando @ pytest.mark.xfail.
  • Omitir pruebas usando @ pytest.mark.skip.
  • Detenga la ejecución de la prueba en n fallas usando pytest --maxfail = <num>.
  • Ejecutar pruebas en paralelo usando pytest -n <num>.
  • Generando resultados xml usando pytest -v --junitxml = "result.xml".

Este tutorial le presentó el framework pytest. Ahora debería poder comenzar a escribir pruebas usando pytest.

Como buena práctica:

  • Cree diferentes archivos de prueba basados ​​en la funcionalidad / módulo que se está probando.
  • Dé nombres significativos a los archivos y métodos de prueba.
  • Tenga suficientes marcadores para agrupar las pruebas en función de varios criterios.
  • Use accesorios cuando sea necesario.