Módulo 5: Coverage y Edge Cases
Boundary Value Analysis y Property-Based Testing con Hypothesis
Boundary Value Analysis y Property-Based Testing con Hypothesis
Descripción de la cápsula
Sabes medir coverage y usar prompts para descubrir edge cases. Pero hay dos técnicas que multiplican tu efectividad: boundary value analysis (sistemático, predecible) y property-based testing con Hypothesis (aleatorio, descubre lo que no imaginabas). Esta cápsula te enseña ambas: cuándo usar valores límite explícitos, cuándo delegar en propiedades matemáticas, y cómo combinar todo con Claude Code.
Al final tendrás un arsenal de tres enfoques complementarios: boundary testing para límites conocidos, property testing para propiedades invariantes, y prompts para que Claude Code piense por ti en escenarios difíciles.
Boundary Value Analysis
Los bugs se concentran en los límites
Una observación empírica en testing: la mayoría de los bugs aparecen en los límites, no en el "medio" del dominio. Si una función acepta edad entre 0 y 120, el bug está casi seguro en -1, 0, 1, 119, 120 o 121 — no en 50, 30 u 80.
| Tipo de boundary | Ejemplo | Por qué falla el código |
|---|---|---|
| Mínimo | 0 | Off-by-one: if age < 0 vs if age <= 0 |
| Máximo | 120 | Lo mismo al otro extremo |
| Cero | 0 | División, índices, lógica especial |
| Uno más/menos | -1, 121 | Justo fuera del rango válido |
| Vacío/lleno | [], [1], lista muy larga | Longitud extrema |
Regla práctica: testea los límites, no el centro
Para una función que valida edad 0–120:
✅ Testea: -1, 0, 1, 119, 120, 121
❌ No pierdas tiempo con: 50, 30, 80 — valores del medio rara vez revelan bugs.
Para una función sobre listas:
✅ Testea: [], [1], [1, 2], lista muy larga (1000+ elementos)
❌ No priorices: [1, 2, 3, 4, 5] — longitud arbitraria intermedia casi nunca es el problema.
Ejemplo: validate_age
def validate_age(age: int) -> bool:
"""Retorna True si age está en [0, 120]."""
return 0 <= age <= 120
Un test naif probaría solo validate_age(25) y pasaría. El boundary test cubre todos los puntos críticos:
import pytest
def validate_age(age: int) -> bool:
"""Retorna True si age está en [0, 120]."""
return 0 <= age <= 120
@pytest.mark.parametrize("age,expected", [
(-1, False), # Debajo del mínimo
(0, True), # Límite mínimo
(1, True), # Justo arriba del mínimo
(119, True), # Justo debajo del máximo
(120, True), # Límite máximo
(121, False), # Arriba del máximo
])
def test_validate_age_boundaries(age, expected):
assert validate_age(age) == expected
Ejecuta:
pytest test_validate_age.py -v
Por qué los boundaries revelan bugs: un ejemplo real
Imagina que un developer escribió por error if age < 1 en vez de if age < 0, pensando que "edad negativa es imposible, no hace falta probarlo." O que usó if age <= 119 cuando el límite debería ser 120. Un test con validate_age(25) pasaría. Pero un boundary test con 0 o 120 fallaría de inmediato. Esa es la potencia: los límites son donde el código toma decisiones distintas, y una decisión equivocada ahí propaga el error a todo el dominio.
Un bug real: off-by-one en límites
Imagina que alguien implementó validate_age con un error común:
def validate_age(age: int) -> bool:
return 0 < age < 120 # Bug: usa < en lugar de <=
Un test con validate_age(25) pasaría. Pero el boundary test con (0, True) fallaría: la función retorna False para edad 0 cuando debería aceptarla. Ese es exactamente el tipo de bug que boundary testing captura. Sin boundaries explícitos, el bug llegaría a producción y usuarios con edad 0 o 120 serían rechazados incorrectamente.
Patrones de boundary testing
Estructura parametrizada
@pytest.mark.parametrize es ideal para boundaries: un test, muchos valores.
@pytest.mark.parametrize("value,expected", [
(min_value - 1, False), # Debajo del rango
(min_value, True), # Límite inferior
(min_value + 1, True), # Justo dentro
(max_value - 1, True), # Justo dentro
(max_value, True), # Límite superior
(max_value + 1, False), # Arriba del rango
])
def test_boundaries(value, expected):
assert function_under_test(value) == expected
Listas: vacío, uno, muchos
@pytest.mark.parametrize("items", [
[], # Vacío
[1], # Un elemento
[1, 2], # Dos elementos (algunas implementaciones fallan aquí)
list(range(10000)), # Muchos (stack, memoria, O(n²) oculto)
])
def test_list_operations(items):
result = process_list(items)
assert result is not None # o la propiedad que corresponda
Strings: vacío, un carácter, Unicode, muy largo
@pytest.mark.parametrize("s", [
"",
"a",
"ñ",
"🔥", # Unicode
"a" * 1_000_000,
])
def test_string_handling(s):
result = normalize(s)
assert isinstance(result, str)
Un bug real: off-by-one en los límites
Imagina que alguien implementa validate_age así:
def validate_age(age: int) -> bool:
"""Retorna True si age está en [0, 120]."""
if age < 0:
return False
if age > 120: # Bug: debería ser >= 121 o mantener > 120
return False
return True
Esa versión es correcta. Pero esta variación tiene un bug típico:
def validate_age_buggy(age: int) -> bool:
"""Versión con bug: usa < en vez de <= en el máximo."""
if age < 0:
return False
if age >= 120: # Bug: 120 debería ser válido, pero lo rechaza
return False
return True
En realidad el bug estaría al otro lado: age > 120 deja pasar 120, pero age >= 120 rechaza 120. El boundary test con 120 lo detectaría. Sin boundary tests, validate_age(25) pasa y crees que todo está bien. En producción, usuarios con 120 años (o datos mal formateados) fallarían.
La lección: Un solo test con valor "normal" (25, 50, 80) no habría encontrado el bug. Los seis valores de boundary sí.
Introducción a property-based testing
De ejemplos a propiedades
En los tests típicos defines ejemplos concretos:
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
En property-based testing defines propiedades invariantes que deben cumplirse para cualquier input válido:
- "Para cualquier entero n,
abs(n) >= 0" - "Para cualquier string s,
reverse(reverse(s)) == s" - "Para cualquier lista lst,
len(sorted(lst)) == len(lst)"
El framework genera cientos (o miles) de inputs aleatorios y verifica que la propiedad se cumpla. Encuentra edge cases que nunca hubieras escrito a mano.
Hipótesis matemática → testing
La idea viene de QuickCheck (Haskell): si puedes enunciar una propiedad matemática, el ordenador puede buscar contraejemplos. Hypothesis es la implementación más usada en Python.
Hypothesis: fundamentos
Instalación
pip install hypothesis
O con Poetry/uv:
poetry add --group dev hypothesis
uv add hypothesis --dev
Primer ejemplo: valor absoluto
from hypothesis import given
from hypothesis import strategies as st
@given(st.integers())
def test_absolute_value_always_positive(n):
result = abs(n)
assert result >= 0
Hypothesis generará muchos enteros (positivos, negativos, cero, muy grandes) y verificará que abs(n) >= 0 siempre se cumple.
Segundo ejemplo: reverse dos veces
def reverse(s: str) -> str:
return s[::-1]
@given(st.text())
def test_reverse_twice_returns_original(s):
assert reverse(reverse(s)) == s
La propiedad: invertir dos veces devuelve el original. Hypothesis prueba con strings vacíos, Unicode, emojis, etc.
Tercer ejemplo: sort preserva longitud
@given(st.lists(st.integers()))
def test_sort_preserves_length(lst):
assert len(sorted(lst)) == len(lst)
La propiedad: ordenar no cambia el número de elementos.
Por qué property-based testing encuentra bugs que tú no ves
Con tests por ejemplo, eliges 3-5 inputs. Con property-based testing, Hypothesis puede generar 100+ en una sola ejecución. Y lo hace de forma inteligente: tiende a probar valores "interesantes" (0, -1, strings vacíos, listas muy largas) más a menudo que valores aleatorios puros. Eso significa que en pocas ejecuciones ya estás cubriendo muchos edge cases que un humano no habría priorizado.
Un ejemplo real: una función que parsea fechas como "YYYY-MM-DD". Un test por ejemplo probaría "2024-01-15". Pero Hypothesis podría generar "2024-02-30" (fecha inválida) o "0000-00-00" — y descubrir que tu parser no valida correctamente.
Estrategias comunes de Hypothesis
Tipos básicos
st.integers() # Cualquier int (incluye negativos, cero, grandes)
st.floats() # Floats (incluye NaN, Inf por defecto)
st.text() # Strings Unicode
st.booleans() # True/False
st.none() # None
Con restricciones
st.integers(min_value=0, max_value=100)
st.floats(min_value=0.01, max_value=10000, allow_nan=False)
st.text(min_size=1, max_size=50)
st.text(alphabet=st.characters(whitelist_categories=("L", "N"))) # Solo letras y números
Estructuras compuestas
st.lists(st.integers()) # Lista de enteros
st.lists(st.integers(), min_size=1) # Al menos un elemento
st.dictionaries(st.text(), st.integers()) # Dict[str, int]
st.tuples(st.integers(), st.text()) # Tupla (int, str)
st.one_of(st.none(), st.integers()) # None o int
st.sampled_from(["a", "b", "c"]) # Uno de estos valores
Ejemplo combinado
@given(
st.lists(st.floats(min_value=0.01, max_value=1000, allow_nan=False))
)
def test_sum_of_non_negative_floats(prices):
total = sum(prices)
assert total >= 0
Propiedades matemáticas típicas
Cuando no sabes qué inputs concretos probar, piensa en propiedades invariantes:
| Propiedad | Ejemplo | Cuándo usar |
|---|---|---|
| Idempotencia | f(f(x)) == f(x) | Normalización, sanitización |
| Conmutatividad | f(a, b) == f(b, a) | Suma, unión de conjuntos |
| Invertibilidad | decode(encode(x)) == x | Serialización, compresión |
| Preservación de estructura | len(sort(lst)) == len(lst) | Ordenamiento, filtrado |
| Cota inferior/superior | min <= clamp(x, min, max) <= max | Funciones de restricción |
| No negatividad | abs(x) >= 0 | Valores absolutos, distancias |
Si tu función implementa una de estas ideas, Hypothesis puede verificarla automáticamente frente a muchos inputs.
Reproducir un fallo de Hypothesis
Cuando Hypothesis encuentra un contraejemplo, guarda la semilla para reproducirlo. Si el test falla, verás algo como:
hypothesis.invalid.Example: @given(...)
Falsifying example: test_foo(x=0, y=-1)
Para reproducir ese caso específico en futuras ejecuciones, usa @reproduce_failure:
from hypothesis import assume, given, reproduce_failure
from hypothesis import strategies as st
@given(st.integers(), st.integers())
def test_something(a, b):
assume(b != 0)
assert (a // b) * b + (a % b) == a # No siempre cierto en Python
# Si falla, Hypothesis te da algo como:
# @reproduce_failure('6.0.0', b'AAEBAA==')
# Añade ese decorador temporalmente para depurar
En la práctica, sueles copiar el ejemplo que falló y escribir un test unitario explícito para ese caso, y luego corregir el código o la propiedad.
Ejemplo real: carrito de compras
Supongamos una clase ShoppingCart simple:
"""Implementación simple de carrito de compras para property testing."""
class ShoppingCart:
def __init__(self):
self._items: list[tuple[str, float]] = []
def add_item(self, name: str, price: float) -> None:
if price < 0:
raise ValueError("Price must be non-negative")
self._items.append((name, price))
def total(self) -> float:
return sum(price for _, price in self._items)
def item_count(self) -> int:
return len(self._items)
Propiedad: el total es la suma de los precios
import pytest
from hypothesis import given
from hypothesis import strategies as st
from shopping_cart import ShoppingCart
@given(
st.lists(
st.floats(min_value=0.01, max_value=10000, allow_nan=False),
min_size=0,
)
)
def test_cart_total_equals_sum_of_prices(prices):
cart = ShoppingCart()
for i, price in enumerate(prices):
cart.add_item(f"item_{i}", price)
assert cart.total() == pytest.approx(sum(prices))
pytest.approx maneja errores de precisión flotante. La propiedad: "para cualquier lista de precios válidos, el total del carrito coincide con la suma".
Propiedad: el número de items es la longitud de la lista añadida
@given(
st.lists(
st.tuples(
st.text(min_size=1),
st.floats(min_value=0.01, max_value=10000, allow_nan=False),
),
min_size=0,
)
)
def test_cart_item_count_matches_added(items):
cart = ShoppingCart()
for name, price in items:
cart.add_item(name, price)
assert cart.item_count() == len(items)
Cuándo usar cada enfoque
| Enfoque | Cuándo usarlo |
|---|---|
| Boundary testing | Conoces los límites exactos: edad 0–120, string max 100 chars, lista no vacía |
| Property testing | Conoces propiedades invariantes pero no todos los inputs posibles |
| Edge case prompts | Quieres que Claude Code piense en escenarios que no se te ocurren |
Los tres se complementan: boundaries para límites conocidos, properties para invariantes matemáticas, prompts para exploración creativa.
Propiedades típicas que puedes verificar
Cuando no se te ocurre qué propiedad testear, estas plantillas ayudan:
| Propiedad | Ejemplo | Cuándo aplica |
|---|---|---|
| Idempotencia | f(f(x)) == f(x) | Normalización, sanitización |
| Conmutatividad | f(a, b) == f(b, a) | Suma, unión de conjuntos |
| Asociatividad | f(f(a, b), c) == f(a, f(b, c)) | Operaciones matemáticas |
| Invarianza de tamaño | len(transform(lst)) == len(lst) | Map, filter sin descartar |
| Orden preservado | Si a < b entonces f(a) <= f(b) | Funciones monótonas |
| Round-trip | decode(encode(x)) == x | Serialización, compresión |
Configuración avanzada de Hypothesis
Reproducir un fallo
Cuando Hypothesis encuentra un contraejemplo, puede mostrar algo como:
Falsifying example: test_sort_preserves_length(lst=[0, 0, -1])
Para reproducir ese mismo ejemplo en la siguiente ejecución, usa @seed o guarda el ejemplo. Hypothesis usa una semilla interna; si el test falla, imprime la semilla al final. Puedes forzarla:
from hypothesis import seed, given
@seed(123456789)
@given(st.lists(st.integers()))
def test_sort_preserves_length(lst):
assert len(sorted(lst)) == len(lst)
Más útil: cuando falla, Hypothesis suele imprimir algo como @seed(12345). Añade ese decorador temporalmente para depurar el mismo ejemplo una y otra vez.
Ajustar número de ejemplos y tiempo
Por defecto Hypothesis genera unos 100 ejemplos por test. Si tu función es costosa o quieres más confianza:
from hypothesis import settings, given
from hypothesis import strategies as st
@settings(max_examples=500)
@given(st.integers())
def test_more_examples(n):
assert abs(n) >= 0
O reducir para tests muy lentos:
@settings(max_examples=20, deadline=500) # 20 ejemplos, 500ms por ejemplo
@given(st.lists(st.integers(), max_size=100))
def test_expensive_operation(lst):
...
Claude Code + Hypothesis
Prompt para generar property-based tests
Puedes pedir a Claude Code que genere tests con Hypothesis:
Genera property-based tests con Hypothesis para esta función. Define las propiedades que siempre deben ser verdaderas, elige las estrategias adecuadas y asegúrate de que el código sea ejecutable desde los imports.
Ejemplo de prompt y resultado
Tu prompt:
Tengo esta función que normaliza precios (redondea a 2 decimales, rechaza negativos). Genera 3 property-based tests con Hypothesis que verifiquen propiedades invariantes.
Código bajo prueba:
def normalize_price(price: float) -> float:
if price < 0:
raise ValueError("Price cannot be negative")
return round(price, 2)
Resultado típico de Claude Code:
from hypothesis import given
from hypothesis import strategies as st
@given(st.floats(min_value=0, allow_nan=False))
def test_normalize_price_non_negative(price):
result = normalize_price(price)
assert result >= 0
@given(st.floats(min_value=0, allow_nan=False))
def test_normalize_price_at_most_two_decimals(price):
result = normalize_price(price)
s = str(result)
if "." in s:
decimal_places = len(s.split(".")[1])
assert decimal_places <= 2
else:
assert True
@given(st.floats(min_value=0, allow_nan=False))
def test_normalize_price_idempotent(price):
first = normalize_price(price)
second = normalize_price(first)
assert first == second
Claude Code puede definir propiedades que tú no habrías considerado (por ejemplo, idempotencia) y elegir estrategias adecuadas.
Ejercicios
Ejercicio 1: Boundary tests para rango de porcentaje
Escribe tests parametrizados para una función validate_discount(percent: float) -> bool que retorna True si 0 <= percent <= 100. Cubre todos los boundaries: debajo, mínimo, justo arriba del mínimo, justo debajo del máximo, máximo, arriba.
Ver solución
import pytest
def validate_discount(percent: float) -> bool:
return 0 <= percent <= 100
@pytest.mark.parametrize("percent,expected", [
(-0.01, False), # Debajo del mínimo
(0.0, True), # Mínimo
(0.01, True), # Justo arriba del mínimo
(99.99, True), # Justo debajo del máximo
(100.0, True), # Máximo
(100.01, False), # Arriba del máximo
])
def test_validate_discount_boundaries(percent, expected):
assert validate_discount(percent) == expected
Ejercicio 2: Property test para suma
La función add(a, b) suma dos números. Escribe un property-based test con Hypothesis que verifique: para cualquier par de enteros (a, b), add(a, b) == add(b, a) (conmutatividad).
Ver solución
from hypothesis import given
from hypothesis import strategies as st
def add(a: int, b: int) -> int:
return a + b
@given(st.integers(), st.integers())
def test_add_commutative(a, b):
assert add(a, b) == add(b, a)
Ejercicio 3: Estrategia para lista no vacía
Necesitas una estrategia que genere listas de strings con al menos un elemento. Escribe la estrategia y un test de propiedad que verifique: "para cualquier lista no vacía de strings, la concatenación de sus elementos tiene longitud >= 1".
Ver solución
from hypothesis import given
from hypothesis import strategies as st
@given(st.lists(st.text(), min_size=1))
def test_concatenation_non_empty(lst):
concat = "".join(lst)
# Si la lista no está vacía, al menos un string puede aportar longitud
# Pero un string puede ser "" — así que la propiedad más segura es:
assert len(lst) >= 1
# O si sabemos que usamos min_size=1 y text(min_size=1):
# assert len(concat) >= 1
Versión más restrictiva (strings no vacíos):
@given(st.lists(st.text(min_size=1), min_size=1))
def test_concatenation_non_empty_strict(lst):
concat = "".join(lst)
assert len(concat) >= 1
Ejercicio 4: Carrito con precios que pueden ser cero
Modifica el test del carrito para permitir precios exactamente 0 (productos gratis). ¿Qué estrategia usarías? ¿Hay que ajustar la implementación de add_item?
Ver solución
Si add_item rechaza price < 0, entonces price == 0 es válido. Solo cambia la estrategia:
@given(
st.lists(
st.floats(min_value=0, max_value=10000, allow_nan=False), # 0 incluido
min_size=0,
)
)
def test_cart_total_with_zero_prices(prices):
cart = ShoppingCart()
for i, price in enumerate(prices):
cart.add_item(f"item_{i}", price)
assert cart.total() == pytest.approx(sum(prices))
Si la implementación actual exige price > 0, tendrías que relajarla para aceptar 0 o usar min_value=0.01 en los tests para que pasen sin cambiar el código. El ejercicio asume que aceptas 0; en ese caso, min_value=0 y la implementación debe permitirlo.
Ejercicio 5: Boundary + property para clamp
Implementa clamp(value, low, high) que retorna value si está en [low, high], o low/high si está fuera. Escribe:
- Boundary tests con parametrize para los valores límite.
- Un property test con Hypothesis: "el resultado siempre está en [low, high]".
Ver solución
from hypothesis import assume, given
from hypothesis import strategies as st
import pytest
def clamp(value: float, low: float, high: float) -> float:
if value < low:
return low
if value > high:
return high
return value
@pytest.mark.parametrize("value,low,high,expected", [
(-1, 0, 10, 0),
(0, 0, 10, 0),
(1, 0, 10, 1),
(9, 0, 10, 9),
(10, 0, 10, 10),
(11, 0, 10, 10),
])
def test_clamp_boundaries(value, low, high, expected):
assert clamp(value, low, high) == expected
@given(
value=st.floats(allow_nan=False),
low=st.floats(allow_nan=False),
high=st.floats(allow_nan=False),
)
def test_clamp_always_in_range(value, low, high):
assume(low <= high) # Hypothesis: assume para filtrar inputs inválidos
result = clamp(value, low, high)
assert low <= result <= high
Ejercicio 6: Prompt a Claude Code
Toma una función propia (o la de clamp del ejercicio anterior) y escribe un prompt para Claude Code pidiendo: (a) boundary tests parametrizados, (b) dos property-based tests con Hypothesis. Copia el prompt y describe qué propiedades sugerirías si Claude Code te pide ayuda.
Ver solución
Prompt ejemplo:
Tengo esta función:
def clamp(value: float, low: float, high: float) -> float: if value < low: return low if value > high: return high return valueNecesito:
- Boundary tests con pytest.mark.parametrize cubriendo value debajo de low, en low, entre low y high, en high, y arriba de high.
- Dos property-based tests con Hypothesis. Propiedades que quiero verificar: (a) el resultado siempre está en [low, high], (b) si value ya está en [low, high], el resultado es value.
Genera el código completo con imports.
Propiedades que podrías sugerir:
- El resultado siempre está entre low y high (inclusivo).
- Si low <= value <= high, entonces clamp(value, low, high) == value.
- clamp es idempotente: clamp(clamp(v,l,h), l, h) == clamp(v,l,h).
Troubleshooting
Hypothesis encuentra un contraejemplo pero no sé cuál
Causa: Hypothesis guarda un ejemplo que falló y lo usa en la próxima ejecución, pero puede que no lo imprima claramente.
Solución: Ejecuta con más verbosidad: pytest -v -s. Hypothesis suele mostrar el ejemplo que falló. También puedes usar @given(...) con print temporal o hypothesis.settings(verbosity=2) para ver qué se genera.
Flaky o tests que a veces pasan y a veces no
Causa: La función bajo prueba o la propiedad tiene comportamiento no determinista, o la estrategia genera valores "problemáticos" (NaN, Inf, muy grandes) que no manejas.
Solución: Restringe las estrategias: allow_nan=False, min_value/max_value acotados. Usa hypothesis.assume() para filtrar inputs que no cumplen precondiciones (por ejemplo, assume(low <= high)).
DeadlineExceeded — el test tarda demasiado
Causa: Hypothesis ejecuta muchas iteraciones y tu función es lenta, o la estrategia genera estructuras muy grandes.
Solución: Limita el tamaño: st.lists(..., max_size=100), st.text(max_size=500). O desactiva el deadline: @settings(deadline=None) (solo para casos justificados).
Property test pasa pero un boundary explícito falla
Causa: La estrategia no está generando el boundary (por ejemplo, 0 o 121 en un rango muy amplio) con alta probabilidad en pocas ejecuciones.
Solución: No confíes solo en property testing para boundaries conocidos. Usa boundary tests explícitos con parametrize para los límites que importan.
ImportError o Hypothesis no está instalado
Causa: Hypothesis no está en el entorno de ejecución.
Solución: pip install hypothesis o poetry add hypothesis --group dev. Verifica que el entorno del IDE y la terminal sean el mismo (which python, poetry env info).
Conexión con el proyecto del módulo
El proyecto del módulo es Suite con 90%+ coverage. Boundary value analysis y property-based testing son herramientas opcionales pero muy recomendables:
- Coverage te dice dónde hay gaps — boundary tests te ayudan a cubrir ramas de condicionales en límites (if x < 0, if x > max, etc.).
- Property-based testing descubre inputs que nunca escribirías a mano y que podrían exponer bugs en funciones matemáticas, validadores o transformaciones.
- Claude Code puede generar ambos tipos de tests si le das el código y un prompt claro.
Flujo sugerido: mide coverage → identifica funciones con lógica en límites → pide boundary tests a Claude Code → añade 1–2 property tests para funciones con propiedades claras (sort, sum, normalización) → mide de nuevo.
Resumen
- ✅ Los bugs se concentran en boundaries: mínimo, máximo, cero, uno más/menos, vacío/lleno.
- ✅ Usa
@pytest.mark.parametrizepara boundary tests sistemáticos en límites conocidos. - ✅ Property-based testing con Hypothesis: defines propiedades invariantes y el framework genera muchos inputs.
- ✅ Estrategias habituales:
st.integers(),st.text(),st.lists(),st.floats()conmin_value/max_value. - ✅ Boundary testing para límites conocidos; property testing para propiedades; prompts para exploración con Claude Code.
- ✅ Puedes pedir a Claude Code que genere boundary y property tests dando el código y requisitos claros.
Próxima cápsula: Proyecto — Suite con 90%+ coverage. Aplicarás todo lo del módulo para llevar un código existente a ≥90% coverage.
Recursos Adicionales
- Hypothesis Documentation — Documentación oficial de Hypothesis
- Hypothesis Strategies — Catálogo de estrategias
- Property-Based Testing with Hypothesis — Artículo sobre property-based testing
- QuickCheck: The Original — Origen del enfoque (Haskell)
- Boundary Value Analysis — Software Testing Fundamentals — Introducción a boundary testing
- Test-Driven Development with Property-Based Testing — Propiedad + TDD
Módulo 5, Cápsula 05 — Testing with Claude Code Guide