PyTest es un marco de trabajo que permite realizar pruebas unitarias para un software en Python. Este documento presenta los conceptos básicos de PyTest, como instalarlo, ejecutar pruebas, y las sentencias comunes utilizadas en este marco.

El propósito de este tutorial es desarrollar pruebas unitarias en Python utilizando PyTest.

Para probar las funcionalidades de PyTest, se utilizará la definición de la clase Persona, la cual cuenta con dos propiedades: Nombre y edad. A partir de estas se tienen los métodos para asignar y obtener las propiedades, así como para determinar el año de nacimiento de la persona. La definición de la clase con sus métodos es la siguiente:

import datetime

class Persona:

def __init__(self, nombre, edad):

self.__nombre = nombre

self.__edad = edad

def asignar_edad(self, edad):

self.__edad = edad

def asignar_nombre(self, nombre):

self.__nombre = nombre

def dar_edad(self):

return(self.__edad)

def dar_nombre(self):

return(self.__nombre)

def calcular_anio_nacimiento(self, ya_cumplio_anios):

anio_actual = datetime.datetime.now().year

if ya_cumplio_anios:

return (anio_actual - self.__edad)

else:

return (anio_actual - self.__edad + 1)

Para instalar este marco de trabajo se debe ejecutar la siguiente instrucción:

pip install pytest

Para ver la versión, se puede ejecutar:

pytest --version

Para hacer las pruebas se recomienda agruparlas en una clase. Esta clase debe crearse dentro de un paquete llamado tests en la raíz del proyecto, y la clase debe llamarse Test<<clase>>, donde <<clase>> hace referencia a la clase cuyos métodos de van a probar. Para este tutorial se creará la clase TestPersona,se almacenará en el archivo test_persona.py dentro del paquete tests, y probará los métodos de la clase Persona.

Para crear la clase de pruebas en el archivo test_persona.py, primero se deben importar las funcionalidades de pytest, así:

import pytest

Y a continuación se importa la clase persona:

from persona import Persona

Luego se crea la clase de prueba, en este caso, TestPersona, así:

class TestPersona:

Y a continuación se crean los métodos de la clase que se utilizarán para hacer las pruebas, donde cada método se define como un método en python, con la excepción que que cada método debe comenzar con la palabra test.

Para probar que la clase con las pruebas ya permite crearlas, se puede crear el siguiente método:

def test_prueba(self):

assert 0 == 0

Y se ejecuta con la instrucción:

pytest test_persona.py

Debe mostrar un resultado similar al siguiente:

======================== test session starts ========================

platform win32 -- Python 3.8.3, pytest-5.4.3, py-1.8.2, pluggy-0.13.1

rootdir: C:\PyTestTutorial\tests

collected 1 item

test_persona.py . [100%]

========================= 1 passed in 0.08s =========================

(PyTestTutorial) C:\PyTestTutorial\tests>pytest test_persona.py

======================== test session starts ========================

platform win32 -- Python 3.8.3, pytest-5.4.3, py-1.8.2, pluggy-0.13.1

rootdir: C:\PyTestTutorial\tests

collected 1 item

test_persona.py . [100%]

========================= 1 passed in 0.16s =========================

El cual muestra que las pruebas se ejecutaron satisfactoriamente. Si se modifica el método, y se escribe de la siguiente manera:

def test_prueba(self):

assert 0 == 0

La prueba debe fallar al ejecutarla, y debe mostrar un mensaje similar al siguiente:

======================== test session starts ========================

platform win32 -- Python 3.8.3, pytest-5.4.3, py-1.8.2, pluggy-0.13.1

rootdir: C:\PyTestTutorial\tests

collected 1 item

test_persona.py F [100%]

============================== FAILURES ==============================

______________________ TestPersona.test_prueba _______________________

self = <tests.test_persona.TestPersona object at 0x02E7A9E8>

def test_prueba(self):

> assert 0 == 1

E assert 0 == 1

test_persona.py:7: AssertionError

====================== short test summary info =======================

FAILED test_persona.py::TestPersona::test_prueba - assert 0 == 1

========================= 1 failed in 0.73s ==========================

En ambos casos se puede observar el resultado de los tests, cuales funcionaron adecuadamente y cuales fallaron (Con las causas de los fallos)

Las pruebas son métodos que al final verifican si una o varias situaciones ses están presentando luego de la ejecución de código. Esas situaciones se verifican a través del uso de la palabra reservada assert, la cual tiene a continuación una condición, que al cumplirse hace que la prueba pase. Un método de prueba puede incluir varios assert y la prueba que se ejecuta en ese método puede arrojar una combinación de condiciones acertadas y erradas.

Adicionalmente, cada método de pruebas, puede procesar información antes de ejecutar los assert, lo que permite preparar datos para el desarrollo de las pruebas.

A continuación se presenta un conjunto de pruebas sobre la clase Persona, donde se verificarán que los métodos funcionen adecuadamente.

En un primer caso, se verificará que el constructor almacena de manera adecuada los datos al crear la clase. Para esto se crea un objeto de la clase Persona con datos iniciales, y se verifica que los datos sean los adecuados:

def test_constructor(self):

persona = Persona(nombre="Diego", edad=25)

assert persona.dar_nombre() == "Diego"

assert persona.dar_edad() == 25

También se puede verificar si al cambiar los datos, los datos almacenados en realidad se actualizaron y no almacenan datos anteriores. Para esto se probarán los métodos para asignar nombre y edad, y se probará que los datos anteriores no existen y que los nuevos quedaron asignados:

def test_asingacion(self):

persona = Persona(nombre="Diego", edad=25)

persona.asignar_edad(28)

persona.asignar_nombre("Adriana")

assert persona.dar_nombre() != "Diego"

assert persona.dar_edad() != 25

assert persona.dar_nombre() == "Adriana"

assert persona.dar_edad() == 28

En las pruebas se pueden hacer verificaciones utilizando las operaciones de Python, por ejemplo, verificar que una cadena se encuentre contenida en otra. En este caso de prueba, se verificará que el nombre contenga una cadena de texto específica.

def test_contiene_texto(self):

persona = Persona(nombre="María Alejandra", edad=22)

assert "Alejandra" in persona.dar_nombre()

Las pruebas también sirven para verificar que ciertos cálculos se hagan de manera adecuada por los métodos que se están probando, por ejemplo, en la prueba del año de nacimiento, como se muestra a continuación:

def test_anio_nacimiento(self):

persona = Persona(nombre="María Alejandra", edad=22)

assert persona.calcular_anio_nacimiento(True) == datetime.datetime.now().year - 22

assert persona.calcular_anio_nacimiento(False) == datetime.datetime.now().year - 22 + 1

Antes de realizar las pruebas, se recomienda enunciar los casos de prueba, es decir, una lista de pruebas a realizar, con las situaciones a verificar y los resultados esperados. Para los casos del apartado anterior, la lista de pruebas corresponde a cada ejemplo, luego se diseñaron las pruebas a partir de situaciones con los datos de objetos de la clase Persona, y luego los resultados esperados con los assert. Cuando no se cuenta con estas lista y estos casos, las pruebas pueden tomar mucho tiempo, al no tener claridad sobre qué se quiere probar. Los criterios de aceptación son, en muchos casos, una fuente de casos de prueba.