Python Unittest frente a Pytest
El objetivo principal de este artículo es discutir dos de los marcos más utilizados para pruebas unitarias en Python, unittest
y pytest
, sus ventajas y desventajas, y cuándo preferir cuál sobre el otro.
Python prueba unitaria
frente a Pytest
Al escribir cualquier software, debemos mantener el proceso de verificación de errores durante todo el proceso de desarrollo. Garantiza que una vez que el software alcance la etapa de lanzamiento, se encuentre una cantidad mínima de errores durante su uso.
Python también tiene una variedad de marcos de prueba que permiten probar el código escrito dándole entradas variadas para verificar su comportamiento.
En caso de que se encuentre algún error, puede corregirse durante las etapas de desarrollo en lugar de “revisiones” después del lanzamiento inicial de la aplicación.
Código de ejemplo:
class Calculate:
def CheckPrime(self, a):
for i in range(a):
if a % i:
return False
return True
def CalcFact(self, a):
if a == 1:
return a
else:
return a * self.fact(a - 1)
El código que se muestra arriba contiene dos funciones llamadas CheckPrime
y CalcFact
, que, como se desprende de sus nombres, verifican números primos y calculan factoriales.
Para garantizar que los métodos de Calcular
funcionen sin problemas, es esencial verificar los errores que pueden surgir al dar resultados variados.
Entonces, ¿cómo podemos hacer eso? Para asegurarnos de que nuestro código esté libre de errores, podemos usar diferentes marcos de prueba para escribir casos de prueba y probar nuestro código encima de ellos para verificar la integridad de nuestro código.
Aunque existen muchos marcos de prueba, dos de los más utilizados son unittest
y pytest
. Vamos a explorarlos uno por uno a continuación.
Prueba unitaria por el marco unittest
unittest
es un marco de pruebas unitarias incluido en la biblioteca estándar de Python. Este marco se inspiró en JUnit
, un marco de Java para pruebas unitarias.
Antes de discutir el funcionamiento de unittest
, es esencial conocer los términos comúnmente utilizados en unittest
(también utilizados en otros marcos relacionados).
Caso de prueba
– Unidad de prueba más pequeña – Por lo general, consta de un soloTest Suite
– Casos de prueba agrupados – Suele ejecutarse uno tras otroTest Runner
: coordina y maneja la ejecución de casos y suites de prueba
Use el marco unittest
para escribir casos de prueba
Dado que la biblioteca estándar de Python ya contiene unittest
, no es necesario descargar ningún módulo externo para comenzar a escribir pruebas unitarias usando unittest
.
Podemos comenzar después de importar el módulo unittest
. Ahora, concentrémonos en el código que hemos visto antes.
Código de ejemplo:
class Calculate:
def CheckPrime(self, a):
for i in range(a):
if a % i:
return False
return True
def CalcFact(self, a):
if a == 1:
return a
else:
return a * self.fact(a - 1)
Para escribir casos de prueba utilizando unittest
, tenemos que seguir una sintaxis específica, a saber, que la clase de prueba es un hijo de unittest.TestCase
, y sus métodos deben comenzar con test_
.
Considere el siguiente código:
import unittest
class Calculate:
def CheckPrime(self, a):
for i in range(2, a):
if a % i == 0:
return False
return True
def CalcFact(self, a):
if a == 1:
return a
else:
return a * self.CalcFact(a - 1)
class TestCalc(unittest.TestCase):
def test_CheckPrime(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CheckPrime(2), True)
self.assertEqual(calc.CheckPrime(3), True)
self.assertEqual(calc.CheckPrime(4), False)
self.assertEqual(calc.CheckPrime(80), False)
def test_CheckFact(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CalcFact(2), 2)
self.assertEqual(calc.CalcFact(3), 6)
Producción :
PS D:\Unittest> python -m unittest a.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
A juzgar por el resultado, podemos ver que todos los casos de prueba pasaron porque todas las afirmaciones fueron exitosas.
Ahora probemos un caso en el que falla el caso de prueba.
def test_CheckFact(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CalcFact(2), 2)
self.assertEqual(calc.CalcFact(3), 6)
# Supposed to throw an error
self.assertEqual(calc.CalcFact(0), 0)
Producción :
PS D:\Unittest> python -m unittest a.py
======================================================================
ERROR: test_CheckFact (a.TestCalc)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Python Articles\a.py", line 34, in test_CheckFact
self.assertEqual(calc.CalcFact(0), 0) # Supposed to throw an error
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
[The previous line is repeated 974 more times]
File "D:\Python Articles\a.py", line 12, in CalcFact
if (a == 1):
RecursionError: maximum recursion depth exceeded in comparison
----------------------------------------------------------------------
Ran 2 tests in 0.004s
FAILED (errors=1)
Como se desprende del código, ejecutamos el script usando python -m unittest <name_of_script.py>
.
Este código funciona sin llamar a los métodos de la clase de prueba porque el módulo unittest
maneja los archivos de script que se le dan en un formato particular.
Dado que nuestro script contenía TestCalc
, la clase secundaria de unittest.TestCase
es instanciada automáticamente por Test Runner
.
Después de la creación de instancias, los métodos de prueba se encuentran dentro de la clase y se ejecutan en orden. Para que un método sea considerado un método de prueba
, debe comenzar con test_
.
Una vez que se encuentran los métodos de prueba, se llaman en orden; en nuestro caso, se llaman tanto test_CheckPrime
como test_CalcFact
. Las afirmaciones se verifican en nuestra implementación y se arroja un error en la salida en caso de un comportamiento inesperado.
De nuestro caso de prueba, que contenía un error, se puede deducir que debido a cómo está escrito el código, comenzó a ocurrir una recursividad infinita en el método CalcFact
, que ahora se puede solucionar gracias al caso de prueba.
En caso de que se pregunte por qué ocurre el error, se debe a que la condición inicial no verifica los números menores que uno.
Pros y contras del marco unittest
Algunas de las ventajas de usar unittest
se enumeran a continuación:
- Incluido en la biblioteca estándar de Python
- Promueve casos de prueba relacionados en un único conjunto de pruebas
- Colección de prueba rápida
- Duración precisa del tiempo de prueba
El unittest
viene con las siguientes desventajas:
- Puede ser difícil de entender
- Sin salida de color
- Puede ser demasiado detallado
Prueba unitaria por Pytest
Framework
A diferencia de unittest
, Pytest
no es un módulo integrado; Tenemos que descargarlo por separado. Sin embargo, instalar Pytest es relativamente fácil; para ello podemos usar pip
y ejecutar el siguiente comando.
pip install pytest
Use Pytest
para escribir casos de prueba
Escribamos algunos casos de prueba usando Pytest
. Sin embargo, antes de comenzar, veamos en qué se diferencia Pytest
de unittest
al escribir casos de prueba. Para pruebas unitarias escritas en Pytest
, tenemos que:
- Cree un directorio separado y coloque los scripts que se van a probar en el directorio recién creado.
- Escribir pruebas en archivos que comiencen con
test_
o terminen con_test.py
. Un ejemplo seríatest_calc.py
ocalc_test.py
.
Considere el siguiente código escrito para casos de prueba usando Pytest
.
def test_CheckPrime():
calc = Calculate()
# Passing different outputs
assert calc.CheckPrime(2) == True
assert calc.CheckPrime(3) == True
assert calc.CheckPrime(4) == False
assert calc.CheckPrime(80) == False
def test_CheckFact():
calc = Calculate()
# Passing different outputs
assert calc.CalcFact(2) == 2
assert calc.CalcFact(3) == 6
# assert calc.CalcFact(0) == 0 # Supposed to throw an error
Producción :
============================================================== test session starts ==============================================================
platform win32 -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: D:\Unittest
collected 2 items
test_a.py
[100%]
=============================================================== 2 passed in 0.04s ===============================================================
Ahora, con un caso de prueba fallido:
============================================================== test session starts ==============================================================
platform win32 -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: D:\Unittest
collected 2 items
test_a.py .F
[100%]
=================================================================== FAILURES ====================================================================
________________________________________________________________ test_CheckFact _________________________________________________________________
def test_CheckFact():
calc = Calculate()
# Passing different outputs
assert calc.CalcFact(2) == 2
assert calc.CalcFact(3) == 6
> assert calc.CalcFact(0) == 0 # Supposed to throw an error
test_a.py:50:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
.
.
.
.
.
RecursionError: maximum recursion depth exceeded in comparison
test_a.py:10: RecursionError
============================================================ short test summary info ============================================================
FAILED test_a.py::test_CheckFact - RecursionError: maximum recursion depth exceeded in comparison
========================================================== 1 failed, 1 passed in 2.42s ==========================================================
Los casos de prueba escritos usando Pytest
son un poco más simples que unittest
; en lugar de crear una clase que fuera hija de unittest.TestCase
, podemos escribir nuestras funciones de prueba simplemente con test_
al comienzo del método.
Pros y contras del marco Pytest
Las siguientes son algunas ventajas de usar el marco Pytest
en Python.
-
Suites de prueba compactas
-
Código repetitivo mínimo
-
Soporte de complementos
-
Presentación de salida ordenada y adecuada
También viene con una desventaja, que se enumera a continuación.
-
A menudo incompatible con otros marcos
Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.
LinkedIn