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.