1.4.-Pruebas con pytest
1.4. Realización de pruebas con pytest¶
Idea principal
Programar no consiste únicamente en escribir código: también hay que comprobar que hace lo que se espera. En este apartado aprenderás a organizar un proyecto Python, a separar las funciones del código de prueba y a automatizar comprobaciones con pytest.
En los apartados anteriores has escrito programas pequeños, has utilizado variables, tipos y operadores, y has empezado a organizar el código mediante funciones. El siguiente paso es probar cada parte mientras la desarrollas. Así puedes detectar pronto un error, corregirlo y volver a comprobarlo sin tener que ejecutar manualmente todo el programa.
Este contenido se relaciona con los siguientes elementos de la normativa del módulo de Programación:
| Código | Descripción |
|---|---|
| RA 1 | Reconoce la estructura de un programa informático, identificando y relacionando los elementos propios del lenguaje de programación utilizado. |
| CE b | Se han creado proyectos de desarrollo de aplicaciones. |
| CE c | Se han utilizado entornos integrados de desarrollo. |
Qué deberías saber al terminar
Al acabar este apartado deberías poder:
- explicar qué es una prueba automatizada y por qué resulta útil;
- crear y activar un entorno virtual para un proyecto Python;
- organizar el código fuente y las pruebas en carpetas separadas;
- definir funciones pequeñas, con entradas y resultados claros;
- escribir pruebas con
asserty ejecutarlas conpytest; - probar varios casos con
pytest.mark.parametrize; - interpretar el resultado de una ejecución de pruebas y corregir el código.
Mapa del tema
Seguiremos un ciclo sencillo de trabajo:
- preparar un entorno aislado;
- escribir una función con un comportamiento esperado;
- crear pruebas para casos normales, límite y erróneos;
- ejecutar las pruebas, interpretar el resultado y mejorar el código.
1. Probar mientras programas¶
Una prueba compara el comportamiento real de un programa con el
comportamiento esperado. Por ejemplo, si una función suma dos números, podemos
establecer que sumar(2, 3) debe devolver 5.
Las pruebas automatizadas son pequeños programas que realizan esa comprobación por nosotros. Se pueden ejecutar muchas veces y dejan un resultado claro:
- correcta (
passed): el resultado coincide con lo esperado; - fallida (
failed): el programa responde de otra forma; - error (
error): la prueba o el programa no han podido ejecutarse, por ejemplo, por un error de sintaxis o una importación incorrecta.
Una prueba no demuestra que no existan errores
Una prueba solo comprueba los casos que se han definido. Si los casos están bien elegidos, aumentan mucho la confianza en el programa, pero no sustituyen el análisis, la revisión del código ni las pruebas de integración.
2. Funciones como unidades que se pueden probar¶
Una función agrupa instrucciones bajo un nombre. Recibe datos mediante sus
parámetros y puede devolver un resultado con return.
def calcular_importe(precio, unidades):
"""Devuelve el importe de una línea de pedido."""
return precio * unidades
La llamada calcular_importe(12.5, 2) produce 25.0. Esta separación es
importante para probar: la función recibe unos datos concretos y podemos
comparar su resultado con el que esperamos, sin tener que introducir datos por
teclado ni comprobar toda la salida de la aplicación.
2.1. Diseñar funciones fáciles de probar¶
En esta unidad no necesitamos aplicar todavía todos los principios de diseño, pero conviene adoptar estas pautas:
- una función debe tener una responsabilidad clara;
- debe recibir la información que necesita mediante parámetros;
- debe devolver un resultado en lugar de mezclar siempre cálculo y
print(); - debe tener nombres expresivos y un docstring breve;
- debe evitar depender de variables globales o de entradas que no podemos controlar durante una prueba.
Por ejemplo, es más fácil probar esta función:
que una función que pide el número y muestra el resultado a la vez:
La segunda función puede ser útil en la interfaz de una aplicación, pero mezcla entrada, procesamiento y salida. La primera contiene la lógica y se puede probar directamente. Después, otra función puede encargarse de hablar con la persona usuaria.
Regla práctica
Si puedes llamar a una función con valores conocidos y comprobar su retorno sin usar el teclado, la pantalla, la red o un fichero, normalmente será más sencilla de probar.
3. Preparar el proyecto y su entorno virtual¶
Python permite instalar paquetes de terceros desde el índice PyPI (Python Package Index). Para evitar que las dependencias de un proyecto interfieran con las de otros, se utiliza un entorno virtual: una carpeta aislada en la que se instalan los paquetes del proyecto.
3.1. Comprobar Python y crear el entorno¶
En Linux y macOS, comprueba la versión instalada con:
En Windows puede ser necesario utilizar python o py. Desde la carpeta raíz
del proyecto, crea el entorno usando el módulo incluido en Python:
La opción venv es suficiente para este ejemplo; no es necesario instalar el
paquete externo virtualenv. Activa el entorno con el comando correspondiente a
tu sistema:
Cuando está activo, suele aparecer (.venv) al principio de la línea de
órdenes. A partir de ese momento, pip instala los paquetes en este proyecto y
no de forma global.
Usar python -m pip ayuda a asegurarse de que pip pertenece al mismo Python
que ejecutará el proyecto. Para salir del entorno virtual:
No se debe subir .venv al control de versiones. Añade estas entradas a
.gitignore:
3.2. Guardar las dependencias¶
Puedes consultar los paquetes instalados con pip list y guardar sus versiones
en requirements.txt:
Otra persona podrá recrear el entorno con:
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip install -r requirements.txt
4. Organizar el código y las pruebas¶
Una organización sencilla separa el código que se entrega de los archivos que lo comprueban:
proyecto/
├── .venv/ # Entorno virtual; no se sube al repositorio
├── src/ # Código fuente
│ ├── __init__.py
│ └── main.py
├── tests/ # Pruebas automatizadas
│ ├── __init__.py
│ └── test_main.py
├── .gitignore
├── requirements.txt
└── README.md
El archivo __init__.py permite tratar src como un paquete en esta estructura.
Puede estar vacío; también puedes incluirlo en tests/ para mantener ambos
directorios como paquetes. En Linux y macOS, el entorno contiene ejecutables en
.venv/bin y paquetes instalados en .venv/lib (en Windows se organizan de
otra forma). Los nombres de las pruebas deben empezar normalmente por test_ o
terminar en _test.py para que pytest las encuentre automáticamente.
En src/main.py guardaremos la función que queremos probar:
5. Escribir la primera prueba¶
En tests/test_main.py importamos la función y usamos una aserción (assert)
para expresar el resultado esperado:
from src.main import sumar
def test_sumar_dos_numeros():
# Preparar y ejecutar
resultado = sumar(2, 3)
# Comprobar
assert resultado == 5
Esta prueba sigue el patrón Preparar, ejecutar y comprobar (Arrange, Act, Assert):
- se preparan los datos de entrada;
- se llama a la función;
- se compara el resultado con el valor esperado.
Una aserción no imprime un mensaje para que lo leamos. Si la condición es cierta, la prueba continúa; si es falsa, pytest informa del valor obtenido y del valor esperado.
Desde la raíz del proyecto, ejecuta todas las pruebas:
También puedes indicar expresamente la carpeta:
Una salida resumida correcta puede ser:
============================= test session starts =============================
collected 1 item
tests/test_main.py . [100%]
============================== 1 passed in 0.02s ===============================
El punto (.) representa una prueba superada. Si cambiamos temporalmente la
aserción a assert resultado == 6, pytest la marcará como fallida y mostrará la
diferencia. Lee ese informe antes de modificar código al azar: suele indicar el
archivo, la línea, la expresión comprobada y los valores obtenidos.
6. Probar varios casos con parametrize¶
Una función no se valida con un único ejemplo. Conviene probar valores normales,
casos límite y, cuando proceda, valores negativos o vacíos. El decorador
pytest.mark.parametrize permite ejecutar la misma prueba con varios conjuntos
de datos:
import pytest
from src.main import sumar
@pytest.mark.parametrize(
"primer_sumando, segundo_sumando, esperado",
[
(0, 0, 0),
(-1, 1, 0),
(5, 5, 10),
(2.5, 1.5, 4.0),
],
)
def test_sumar_varios_casos(primer_sumando, segundo_sumando, esperado):
assert sumar(primer_sumando, segundo_sumando) == esperado
La lista contiene casos independientes. Si uno falla, pytest identifica también los parámetros utilizados, lo que facilita localizar el problema.
Elige casos con intención
No se trata de añadir valores al azar. Pregúntate qué puede ocurrir en el uso real: el caso habitual, el mínimo, el máximo, el cero, un valor negativo, una colección vacía o un dato incorrecto, según la función.
7. Probar errores esperados¶
Una función puede rechazar entradas que no tienen sentido. Por ejemplo, una
división no puede utilizar cero como divisor. Si la función documenta que lanza
ValueError, pytest permite comprobarlo con pytest.raises:
import pytest
def dividir(dividendo, divisor):
"""Divide dos números y rechaza el divisor cero."""
if divisor == 0:
raise ValueError("El divisor no puede ser cero")
return dividendo / divisor
def test_dividir_rechaza_cero():
with pytest.raises(ValueError, match="divisor"):
dividir(10, 0)
La prueba comprueba tanto el tipo de excepción como parte de su mensaje. De esta forma se documenta el comportamiento esperado y se evita que el programa falle de manera inesperada.
8. Ejemplo completo de ciclo de trabajo¶
Cuando desarrolles una nueva función, puedes seguir esta secuencia:
- Define el comportamiento: escribe qué recibe la función y qué debe devolver.
- Prepara casos: incluye al menos un caso normal y otro que pueda revelar un error.
- Implementa lo mínimo: crea la función y ejecútala con pytest.
- Lee el informe: distingue entre una prueba fallida y un error de configuración o de sintaxis.
- Corrige y repite: cambia una cosa cada vez y vuelve a ejecutar todas las pruebas.
- Amplía la cobertura: añade casos cuando descubras una situación que no estaba contemplada.
Este ciclo conecta la programación con la depuración. Una prueba fallida no es un fracaso: es información concreta sobre una diferencia entre lo que el código hace y lo que se había especificado.
9. Buenas prácticas y errores frecuentes¶
| Situación | Por qué ocurre | Cómo evitarlo |
|---|---|---|
| Instalar paquetes sin entorno virtual | Las dependencias se mezclan entre proyectos. | Crear .venv, activarlo y usar python -m pip. |
| Guardar pruebas junto al código | Cuesta distinguir qué se ejecuta en la aplicación y qué la comprueba. | Usar una carpeta tests/ y nombres test_*.py. |
| Probar solo un caso | Un ejemplo puede ocultar errores en cero, negativos o límites. | Diseñar varios casos representativos. |
Usar print() en vez de una aserción |
La salida hay que revisarla manualmente y no expresa el resultado esperado. | Comparar con assert. |
Mezclar input() con la lógica |
La función depende del teclado y es difícil de repetir. | Separar entrada, procesamiento y salida. |
| Ejecutar pytest desde otra carpeta | Las importaciones o la detección de archivos pueden fallar. | Ejecutarlo desde la raíz del proyecto. |
Confundir failed con error |
No se distingue un resultado incorrecto de un problema de ejecución. | Leer el traceback y comprobar primero sintaxis e importaciones. |
Subir .venv al repositorio |
Se versionan muchos archivos dependientes del equipo. | Añadir .venv/ a .gitignore. |
No uses sudo para instalar pytest en este proyecto
Instalar paquetes globalmente puede romper la configuración de Python del sistema y ocultar qué dependencias necesita la aplicación. Trabaja con el entorno virtual activado. Si tu equipo usa una configuración distinta, sigue las instrucciones del centro o consulta a quien lo administre.
10. Resumen¶
- Las pruebas automatizadas permiten comprobar el comportamiento esperado cada vez que se modifica el código.
- Las funciones con una responsabilidad clara, parámetros explícitos y un valor de retorno son más fáciles de probar.
- Un entorno virtual aísla las dependencias del proyecto y
requirements.txtpermite reproducirlas. - pytest descubre archivos y funciones de prueba siguiendo convenciones de
nombres y utiliza
assertpara comprobar resultados. pytest.mark.parametrizeevita duplicar código al probar muchos casos, ypytest.raisescomprueba errores esperados.- El informe de pytest orienta la depuración: hay que leerlo, corregir y volver a probar.
Idea clave
Una persona programadora profesional no espera al final para comprobar su programa. Escribe una parte pequeña, define qué resultado espera, la prueba, interpreta el informe y continúa. Probar mientras aprendes te ayuda a entender mejor las funciones y a construir programas más fiables.
11. Para seguir practicando¶
- Escribe
es_par(numero)y pruébala con0, números positivos y negativos. - Crea una función que convierta grados Celsius a Fahrenheit y añade varios
casos con
pytest.mark.parametrize. - Diseña una función que rechace una lista vacía y comprueba la excepción con
pytest.raises.
Bibliografía y fuentes¶
- Documentación oficial de Python: entornos virtuales.
- Documentación oficial de Python: módulos.
- Documentación oficial de pytest.
- pytest: parametrización de pruebas.
- pytest: comprobación de excepciones.