En este tema aplicaremos TDD al diseño incremental de funciones puras. Una función pura es especialmente cómoda para practicar TDD porque no depende de archivos, base de datos, red, tiempo ni estado externo.
Construiremos una función que convierte un puntaje numérico en una calificación textual. La función crecerá ejemplo por ejemplo.
Una función pura cumple dos condiciones principales:
Por ejemplo:
def doble(numero):
return numero * 2
Las funciones puras son fáciles de probar porque solo necesitamos preparar entradas y verificar salidas. No hace falta configurar dependencias externas.
Esto permite concentrarse en el ciclo rojo, verde y refactor sin ruido adicional.
Crearemos una función llamada obtener_calificacion. Recibirá un puntaje entre 0 y 100 y devolverá una letra.
Empezaremos solo con esta regla. Las demás calificaciones aparecerán mediante nuevas pruebas.
Usaremos estos archivos:
src/academico/calificaciones.py
tests/test_calificaciones.py
La función de producción estará en calificaciones.py.
Escribimos una prueba para un puntaje claramente dentro del rango de A.
Archivo a crear: tests/test_calificaciones.py
from academico.calificaciones import obtener_calificacion
def test_devuelve_a_si_puntaje_es_noventa_o_mas():
assert obtener_calificacion(95) == "A"
Ejecutamos:
python -m pytest
La prueba debe fallar porque la función todavía no existe.
Creamos la función con lo mínimo para pasar la prueba.
Archivo a crear: src/academico/calificaciones.py
def obtener_calificacion(puntaje):
return "A"
Ejecutamos python -m pytest. La prueba debería pasar.
Ahora agregamos la calificación B:
Escribimos una prueba nueva.
Archivo a modificar: tests/test_calificaciones.py
from academico.calificaciones import obtener_calificacion
def test_devuelve_a_si_puntaje_es_noventa_o_mas():
assert obtener_calificacion(95) == "A"
def test_devuelve_b_si_puntaje_esta_entre_ochenta_y_ochenta_y_nueve():
assert obtener_calificacion(85) == "B"
Ejecutamos python -m pytest. La segunda prueba debe fallar porque la función devuelve siempre "A".
Agregamos la condición necesaria.
Archivo a modificar: src/academico/calificaciones.py
def obtener_calificacion(puntaje):
if puntaje >= 90:
return "A"
return "B"
Ejecutamos:
python -m pytest
Las pruebas para A y B deberían pasar.
Sumamos una tercera regla:
Primero escribimos la prueba.
Archivo a modificar: tests/test_calificaciones.py
from academico.calificaciones import obtener_calificacion
def test_devuelve_a_si_puntaje_es_noventa_o_mas():
assert obtener_calificacion(95) == "A"
def test_devuelve_b_si_puntaje_esta_entre_ochenta_y_ochenta_y_nueve():
assert obtener_calificacion(85) == "B"
def test_devuelve_c_si_puntaje_esta_entre_setenta_y_setenta_y_nueve():
assert obtener_calificacion(75) == "C"
Ejecutamos python -m pytest. La nueva prueba debe fallar.
La nueva prueba justifica otra condición.
Archivo a modificar: src/academico/calificaciones.py
def obtener_calificacion(puntaje):
if puntaje >= 90:
return "A"
if puntaje >= 80:
return "B"
return "C"
Ejecutamos python -m pytest. Si todo pasa, seguimos en verde.
Agregamos una regla final para puntajes menores que 70:
Esta regla cubrirá los puntajes que no entran en A, B o C.
Archivo a modificar: tests/test_calificaciones.py
from academico.calificaciones import obtener_calificacion
def test_devuelve_d_si_puntaje_es_menor_que_setenta():
assert obtener_calificacion(60) == "D"
Agrega esta prueba junto con las anteriores y ejecuta python -m pytest. Debe fallar porque la función devuelve "C" para todo puntaje menor que 80.
Completamos la función:
Archivo a modificar: src/academico/calificaciones.py
def obtener_calificacion(puntaje):
if puntaje >= 90:
return "A"
if puntaje >= 80:
return "B"
if puntaje >= 70:
return "C"
return "D"
Ejecutamos la suite:
python -m pytest
Las pruebas tienen la misma forma. Podemos expresarlas como ejemplos parametrizados.
Archivo a modificar: tests/test_calificaciones.py
import pytest
from academico.calificaciones import obtener_calificacion
@pytest.mark.parametrize(
"puntaje, esperado",
[
(95, "A"),
(85, "B"),
(75, "C"),
(60, "D"),
],
)
def test_obtener_calificacion_segun_puntaje(puntaje, esperado):
assert obtener_calificacion(puntaje) == esperado
Ejecutamos python -m pytest. Si todo pasa, el refactor fue seguro.
Los límites son importantes en reglas por rangos. Podemos agregar ejemplos exactos:
Archivo a modificar: tests/test_calificaciones.py
import pytest
from academico.calificaciones import obtener_calificacion
@pytest.mark.parametrize(
"puntaje, esperado",
[
(90, "A"),
(80, "B"),
(70, "C"),
(69, "D"),
],
)
def test_obtener_calificacion_en_limites_de_rango(puntaje, esperado):
assert obtener_calificacion(puntaje) == esperado
Estos casos ayudan a detectar errores como usar > donde correspondía >=.
Agrega una nueva calificación "E" para puntajes menores que 50. Hazlo con TDD:
obtener_calificacion(40).python -m pytest y verifica que falle.50.Antes de continuar, verifica lo siguiente:
python -m pytest después de cada cambio relevante.En este tema usamos TDD para diseñar una función pura de manera incremental. Empezamos con una regla pequeña, agregamos nuevos ejemplos, generalizamos la implementación y refactorizamos las pruebas.
En el próximo tema trabajaremos con casos borde, valores inválidos y excepciones guiadas por pruebas.