Práctica 1.6: Uso de pytest
P6 - Funciones y pruebas unitarias con pytest¶
Partimos del entorno de P5. Separar una función de la entrada y la salida permite probar la lógica con datos controlados. Una prueba fallida aporta información: no es un motivo para ocultar el error ni para cambiar la prueba sin analizarlo.
1. Objetivos¶
- Definir funciones reutilizables con parámetros y
return. - Organizar código en
src/y pruebas entests/. - Escribir aserciones con
assert, parametrizar casos y comprobar excepciones. - Ejecutar pytest desde el terminal y desde VS Code e interpretar su informe.
2. Relación con RA1 y criterios de evaluación¶
Esta práctica apoya el RA1, especialmente CE b y CE c, mediante la creación del proyecto y el uso del IDE. Las funciones y las pruebas son una herramienta de desarrollo para demostrar ese trabajo, no un CE adicional.
3. Conceptos previos¶
Una función es un bloque de código reutilizable. Favorece dos ideas importantes:
- Reutilización: la misma función puede llamarse varias veces y desde diferentes partes del programa.
- Modularización: un programa complejo se divide en partes pequeñas que se pueden entender, mantener y probar por separado.
Una definición de función incluye def, un nombre, parámetros opcionales, dos
puntos, un bloque indentado y, cuando corresponda, una sentencia return:
Las pruebas unitarias son pequeños programas que comprueban una unidad de código, normalmente una función. Se ejecutan de forma aislada y repetible. En proyectos profesionales pueden ejecutarse automáticamente después de cada cambio mediante integración continua; en esta práctica las ejecutarás desde el terminal y desde VS Code.
4. Pasos a seguir¶
4.1. Crear una función¶
En src/main.py escribe:
def suma(numero1, numero2):
"""Devuelve la suma de dos números."""
return numero1 + numero2
def dividir(dividendo, divisor):
"""Divide dos números y rechaza la división entre cero."""
if divisor == 0:
raise ValueError("El divisor no puede ser cero")
return dividendo / divisor
def main():
print(f"La suma de 3 + 2 es {suma(3, 2)}")
if __name__ == "__main__":
main()
La función se puede reutilizar con print(suma(3, 2)),
print(suma(25, 40)) o total = 100 + suma(25, 40). No mezcles input() con
la lógica que quieres probar.
También puedes mostrar el resultado con distintas formas de construir el texto:
print("La suma de 3 + 2 es {}".format(suma(3, 2)))
print("La suma de", 3, "+", 2, "es", suma(3, 2))
print("La suma de " + str(3) + " + " + str(2) + " es " + str(suma(3, 2)))
texto = "La suma de "
texto += str(3) + " + " + str(2)
texto += " es " + str(suma(3, 2))
print(texto)
print(f"La suma de 3 + 2 es {suma(3, 2)}")
Estas variantes retoman los contenidos de entrada, conversión y formato de
cadenas trabajados en P2. La prueba, sin embargo, debe comprobar el retorno de
suma, no comparar un texto mostrado por pantalla.
4.2. Escribir y ejecutar la primera prueba¶
En tests/test_main.py escribe:
from src.main import dividir, suma
def test_suma_dos_numeros():
resultado = suma(2, 3)
assert resultado == 5
def test_suma_casos_sencillos():
assert suma(0, 0) == 0
assert suma(100, -100) == 0
Con el entorno activo y desde la raíz del proyecto:
Los archivos test_*.py y las funciones test_* se descubren automáticamente.
Si la importación falla, revisa la raíz desde la que ejecutas pytest y la
estructura de carpetas. Puedes añadir __init__.py vacío en src/ y tests/
si tu configuración del IDE lo necesita. Otra alternativa, especialmente si
usas una carpeta src separada, es crear pytest.ini en la raíz:
En ese caso, el import de la prueba puede escribirse como from main import
suma. Elige una de las dos configuraciones y mantenla igual en todo el
proyecto; no mezcles rutas de importación sin comprobarlas.
Recuerda estas reglas al escribir pruebas:
- importa la función desde el módulo donde está definida;
- nombra las funciones de prueba con el prefijo
test_; - separa cada comportamiento que quieras comprobar en una prueba clara;
- utiliza
assertpara expresar el resultado esperado; - deja que pytest capture y explique los fallos en lugar de ocultarlos.
4.3. Parametrizar y probar errores¶
Amplía tests/test_main.py:
import pytest
from src.main import dividir, suma
@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_suma_varios_casos(primer_sumando, segundo_sumando, esperado):
assert suma(primer_sumando, segundo_sumando) == esperado
def test_dividir_rechaza_cero():
with pytest.raises(ValueError, match="divisor"):
dividir(10, 0)
Prueba casos normales, límites, negativos y errores esperados. Para observar un fallo controlado, cambia temporalmente una expectativa, ejecuta pytest, lee el informe y restáurala. No cambies el test para esconder un error del código.
4.4. Ejecutar desde VS Code¶
Selecciona el intérprete de .venv, abre la vista Testing, configura pytest
si VS Code lo solicita y ejecuta una prueba o toda la carpeta. Comprueba también
el botón de depuración. El terminal y el IDE deben usar el mismo entorno.
5. Comprobaciones de finalización¶
- Todas las pruebas pasan con
python -m pytest -v. - Se ha visto al menos un caso parametrizado y una excepción esperada.
- El informe permite distinguir
passed,failedyerror. - Una prueba se ejecuta desde la vista Testing de VS Code.
6. Ejercicios y entregables¶
6.1. Comparar números¶
Crea compara_numeros.py y test_compara_numeros.py. La función recibe dos
números y devuelve el mayor, o 0 si son iguales. Prueba números positivos,
negativos e iguales.
6.2. Factorial desde el IDE¶
Crea calcula_factorial.py y sus pruebas. Para entradas de 0 a 10 devuelve
el factorial; para cualquier otro valor devuelve -1. Recuerda que 0! = 1.
Ejecuta las pruebas desde la vista Testing, provoca un error en el código y
documenta la salida antes de corregirlo.
Entrega los módulos, los archivos de prueba, la estructura del proyecto y capturas del terminal y de VS Code con una ejecución correcta y otra fallida.