3. TDD
TDD (Test Driven Development) es la práctica de testing más utilizada por desarrolladores. Consiste en un flujo opuesto al testing tradicional: escribes primero la prueba y solo añades código nuevo cuando esa prueba falla. Suena raro al principio. Veamos el ciclo con la creación de una espada.
- Creas la funda donde debe encajar.
- Forjas una espada e intentas introducirla. La primera vez no entrará.
- Das los golpes mínimos para acercarla a la forma adecuada.
- Vuelves a probar. Si sigue sin entrar, sigues trabajando.
- Ya entra suavemente.
- Refactorizas: quitas impurezas, marcas del trabajo, y le das brillo.
:quality(85)/https://andros.dev/static/img/courses/testing/tdd-ciclo.jpg)
Flujo
Más que un framework, TDD es una metodología. Tiene un flujo de trabajo muy estricto que da más prioridad a la prueba que al código.
:quality(85)/https://andros.dev/static/img/courses/testing/tdd-flujo.jpg)
- Creas un test.
- Ejecutas todos los tests anteriores y el nuevo. La primera vez fallará.
- Escribes el código más simple que lo pase.
- Ejecutas todos los tests. Si falla, vuelves al paso anterior.
- Refactorizas y ejecutas los tests en cada cambio: mueves el código a su sitio, quitas repeticiones, documentas, divides funciones grandes en pequeñas. Si algo falla, vuelves atrás.
- Creas el test de la siguiente característica y vuelves a empezar el ciclo.
Ejemplo
Vamos a escribir una función que nos diga el estado del agua (sólido, líquido o gaseoso) según la temperatura.
1. Empiezas por el esqueleto
Creo un archivo water.py con el código mínimo:
def get_state(temperature):
"""Return 'solid', 'liquid' or 'gas' for a given temperature in Celsius."""
return ""
2. Creas el test
En test_water.py compruebo que devuelve solid entre -273 grados (el mínimo) y 0:
import pytest
from water import get_state
@pytest.mark.parametrize("temperature", range(-273, 1))
def test_solid(temperature):
assert get_state(temperature) == "solid"
3. Ejecutas el test
pytest
4. ¿Falla? Al siguiente punto.
Falla, claro. Todavía no hay lógica.
5. Escribes el código mínimo que lo pase
def get_state(temperature):
if temperature <= 0:
return "solid"
Ejecuto de nuevo. ¡Lo pasa! Ahora el siguiente test, ¿líquido?
@pytest.mark.parametrize("temperature", range(1, 100))
def test_liquid(temperature):
assert get_state(temperature) == "liquid"
Ejecuto... y falla, lógico. Refactorizo:
def get_state(temperature):
if temperature <= 0:
return "solid"
if 0 < temperature < 100:
return "liquid"
Lo pasa. Y así, la rueda sigue girando hasta cubrir todos los casos.
6. ¿No puedes hacer más tests? Terminaste
Mi función está completa y probada:
def get_state(temperature):
if temperature <= 0:
return "solid"
if 0 < temperature < 100:
return "liquid"
return "gas"
Triangulación: rompe el miedo al folio en blanco
¿Y si no sé por dónde empezar a escribir la prueba? La triangulación te sortea el bloqueo inicial. Ojo, no la confundas con el TDD clásico.
En lugar de escribir primero la prueba que falla, escribes tres pruebas que pasan, cada una con un caso un poco más general, y refactorizas para eliminar la duplicación entre ellas.
Supongamos que queremos una función que detecte si una palabra es anagrama de otra (mismas letras, distinto orden).
Primera prueba, un caso muy simple:
def test_anagram_simple():
assert is_anagram("amor", "roma") is True
def is_anagram(s1, s2):
return sorted(s1) == sorted(s2)
Segunda prueba, ahora con espacios:
def test_anagram_with_spaces():
assert is_anagram("amor", "a rom") is True
def is_anagram(s1, s2):
return sorted(s1.replace(" ", "")) == sorted(s2.replace(" ", ""))
Tercera prueba, con mayúsculas:
def test_anagram_with_uppercase():
assert is_anagram("Amor", "ROMA") is True
def is_anagram(s1, s2):
return sorted(s1.replace(" ", "").lower()) == sorted(s2.replace(" ", "").lower())
Cada paso generaliza un poco más. Al final tienes una función que resuelve el caso completo, y un puñado de tests que puedes unificar con parametrización.
Material de apoyo
Actividad 1
Genera tu propio validador de contraseñas. Según cumpla unos requisitos u otros, devolverá su nivel de seguridad, entre 0 y 5. La función será password_strength.
- Más de 10 caracteres (nivel +1).
- Más de 20 caracteres (nivel +1).
- Alfanumérica (nivel +1).
- Contiene caracteres especiales como
/,_,$... (nivel +1). - Uno o varios espacios (nivel +1).
Usa la metodología TDD.
Actividad 2
Crea una función que salude.
greet("Conan")
# Devolverá -> 'Hola Conan'
Usa la metodología TDD.
Actividad 3
Crea una función que devuelva una lista con los números pares desde 0 hasta el argumento que indiques.
even_numbers(6)
# Devolverá -> [0, 2, 4, 6]
Usa la metodología TDD y aprovecha la triangulación para empezar.
Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.
Desafíos de programación atemporales y multiparadigmáticos
Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.
Comprar el libro¿Me invitas a un café?
Así sigo escribiendo sin publicidad ni muros de pago.
¡Claro, te invito!
Comentarios
Todavía no hay ningún comentario.