Realizar pruebas de código en Python

Python como tantos otros lenguajes de programación permite realizar pruebas al código para descubrir errores. Para ello existen varias técnicas y métodos que se pueden utilizar, como por ejemplo, inserir print() (Print debugging) en el código, el uso de los debuggers incluidos en el IDE, y la realización de pruebas de distinto tipo: pruebas unitarias (unit tests), pruebas de integración (iuntegration tests), etc.

Aquí nos centraremos en la realización de ejemplos básicos de pruebas (tests) que utilizan las bibliotecas, frameworks y test runners disponibles para Python para encontrar bugs, controlar el desempeño de la aplicación, o buscar fallas de seguridad.

Python tests

Clasificación de pruebas de código

Se pueden clasificar los tests de varias maneras: en primer lugar manuales o automatizados. Una simple prueba manual exploratoria (Exploratory Test) es cuando se navega por la aplicación haciendo clic en los botones y en las opciones de los menúes para verificar si realizan las actividades para las que fueron programadas.

Si creamos un script siguiendo estos pasos ya lo estaríamos convirtiendo en un test automatizado, para lo cual podemos utilizar las bibliotecas disponibles de Python, como mencionado anteriormente. Las pruebas automatizadas son un componente clave de la integración continua (continuous integration – CI) y la entrega continua (continuous deliver – CD).

. Ver Uso de python en ambientes devops.

python tests

Pruebas unitarias – TDD

Otra manera de clasificar las pruebas es por tipo de prueba. Las pruebas unitarias (Unit Tests) comprueban el correcto funcionamiento de una unidad de código (una clase por ejemplo). Esto se suele denominar desarrollo guiado por pruebas (Test Driven Development – TDD) si creamos los casos de pruebas antes de comenzar a programar.

Las pruebas de integración (Integration Tests), por su parte, lo hacen para todo el sistema en su conjunto. Además, también podemos hablar de las pruebas funcionales (Functional Tests) que se centran en los requisitos de negocio de una aplicación, de punta a punta (End to End Tests) que replican el comportamiento de los usuarios en la aplicación, de regresión (Regression Tests), de humo (Smoke tests), de aceptación (Acceptance Tests), y de rendimiento (Performance Tests).

 

Unittest, nose2 y pytest

Existen varios framewoks y test runners disponibles para Python. En la wiki de Python tenemos una extensa lista con una breve descripción y el enlace correspondiente: https://wiki.python.org/moin/PythonTestingToolsTaxonomy. Para los siguientes ejemplos utilizaremos Unittest, Nose2 y Pytest.

El framework Unittest https://docs.python.org/3.9/library/unittest.html viene ya incluido en Python desde su versión 2.1 y provee entre otras cosas, automatización de pruebas y crear colecciones con las mismas.

 

Ejemplo Unittest

Creamos un ambiente virtual en nuestra máquina local e instalamos pip:

python3 -m venv pythonTests
. pythonTests/bin/activate
python3 -m pip install –upgrade pip

Creamos un repositorio en https://github.com/ y lo clonamos en nuestra máquina local:

git clone git@github.com:fortinux/python_tests.git
cd python_tests/

Creamos el primer fichero para verificar si puede pasar el test unitario:

vim test_suma.py

# Fichero test_suma.py
def test_suma():
assert sum([1, 1, 2, 3, 5, 8]) == 20, «Debe sumar 20»

if __name__ == «__main__»:
test_suma()
print(«Ha superado el test»)

Lo ejecutamos:

python test_suma.py

Si vamos a utilizar un workflow de CI/CD es conveniente instalar flake8 para analizar si nuestro código tiene errores:

pip install flake8

Lo configuramos:

pip freeze > requirements.txt

Lo ejecutamos con:

flake8 –statistics

Creamos otro ejemplo con dos pruebas unitarias:

vim test_suma2.py

# Fichero test_suma2.py
def test_suma():
assert sum([1, 1, 2, 3, 5, 8]) == 20, «Debe sumar 20»

def test_suma_tuple():
assert sum((1, 1, 2, 3, 5, 8)) == 20, «Debe sumar 20»

if __name__ == «__main__»:
test_suma()
test_suma_tuple()
print(«Ha superado los tests»)

Lo ejecutamos:

python test_suma2.py

Creamos un ejemplo para utilizar unittest:

vim test_suma_unittest.py
# Fichero suma_unittest.py
import unittest

class TestSum(unittest.TestCase):

def test_suma(self):
self.assertEqual(sum([1, 1, 2, 3, 5, 8]), 20, «Debe sumar 20»)

def test_suma_tuple(self):
self.assertEqual(sum((1, 1, 2, 3, 5, 8)), 20, «Debe sumar 20»)

if __name__ == «__main__»:
unittest.main()

Lo ejecutamos con:

python test_suma_unittest.py

 

Ejemplos Nose2 y Pytest

Nose2 se propone extender unittest mediante plugins para hacer más fácil de entender las pruebas que realizamos. La documentación se encuentra en https://docs.nose2.io/en/latest/.

Instalamos nose2 para utilizarlo en las pruebas:

pip install nose2

Lo ejecutamos para analizar todos los ficheros del directorio actual que comienzan con test*.py:

python -m nose2

El framework pytest https://docs.pytest.org/en/latest/ hace fácil escribir pequeñas pruebas, además de poder escalar y soportar complejas pruebas funcionales para aplicaciones y bibliotecas.

Creamos un fichero para hacer las pruebas con pytest:

vim test_suma_pytest.py
# Fichero suma_pytest.py
def test_suma():
assert sum([1, 1, 2, 3, 5, 8]) == 20, «Debe sumar 20»

def test_suma_tuple():
assert sum((1, 1, 2, 3, 5, 8)) == 20, «Debe sumar 20»

Lo ejecutamos con:

pytest

El repositorio con los s
https://github.com/fortinux/python_tests

 

Referencias

https://www.atlassian.com/continuous-delivery/software-testing/types-of-software-testing
https://realpython.com/python-testing/