¿Qué Son Cobertura de Sentencias y Decisiones?
Cobertura de sentencias y decisiones son técnicas de test design de caja blanca (basadas en estructura) que miden qué tan exhaustivamente los test cases ejercitan el código fuente. A diferencia de las técnicas de caja negra que se enfocan en requerimientos, estas se enfocan en la estructura del código.
Por Qué Importa la Cobertura de Código
Código que nunca se ejecuta durante el testing es código que nunca se verifica. Las métricas de cobertura te dicen:
- Qué líneas de código tus tests realmente ejecutan
- Qué branches de puntos de decisión permanecen sin probar
- Dónde agregar tests para mejor cobertura estructural
Cobertura de Sentencias
Definición: El porcentaje de sentencias ejecutables ejecutadas por la suite de tests.
Cobertura de Sentencias = (Sentencias Ejecutadas / Total Sentencias Ejecutables) x 100%
Ejemplo:
def calcular_descuento(precio, es_miembro): # Línea 1
descuento = 0 # Línea 2
if es_miembro: # Línea 3
descuento = precio * 0.10 # Línea 4
precio_final = precio - descuento # Línea 5
return precio_final # Línea 6
Test case 1: calcular_descuento(100, True) → Ejecuta líneas 1, 2, 3, 4, 5, 6
Cobertura de sentencias = 6/6 = 100%
Pero solo probamos con es_miembro=True. Nunca probamos cuando es falso. Esta es la debilidad de la cobertura de sentencias.
Cobertura de Decisiones (Branch Coverage)
Definición: El porcentaje de resultados de decisiones (branches verdadero/falso) ejecutados.
Cobertura de Decisiones = (Resultados de Decisiones Ejecutados / Total Resultados) x 100%
Para el mismo código:
- Decisión en línea 3:
es_miembro→ tiene 2 resultados: True y False
Test case 1: calcular_descuento(100, True) → Decisión True
Test case 2: calcular_descuento(100, False) → Decisión False
Cobertura de decisiones = 2/2 = 100%
Relación: Cobertura de Decisiones Subsume Cobertura de Sentencias
100% cobertura de decisiones → 100% cobertura de sentencias (siempre verdadero) 100% cobertura de sentencias → 100% cobertura de decisiones (NO siempre verdadero)
Cálculo de Cobertura: Paso a Paso
def categorizar_edad(edad): # S1
if edad < 0: # D1
return "Inválida" # S2
elif edad < 18: # D2
return "Menor" # S3
elif edad < 65: # D3
return "Adulto" # S4
else: # D3-false
return "Senior" # S5
Test cases mínimos para 100% cobertura de decisiones:
| TC | Input | D1 | D2 | D3 | Return |
|---|---|---|---|---|---|
| TC1 | edad = -5 | V | - | - | “Inválida” |
| TC2 | edad = 10 | F | V | - | “Menor” |
| TC3 | edad = 30 | F | F | V | “Adulto” |
| TC4 | edad = 70 | F | F | F | “Senior” |
4 test cases logran 100% cobertura de sentencias y decisiones.
Análisis Avanzado de Cobertura
Vacíos de Cobertura y Qué Significan
| Tipo de Vacío | Significado | Riesgo |
|---|---|---|
| Sentencia no cubierta | Existe código que ningún test ejecuta | Código muerto o lógica sin probar |
| Branch True no cubierto | La condición nunca fue verdadera | Camino positivo sin probar |
| Branch False no cubierto | La condición nunca fue falsa | Camino de error sin probar |
Limitaciones de Cobertura de Sentencias y Decisiones
Lo que 100% de cobertura NO garantiza:
- Código faltante. La cobertura mide lo que existe, no lo que falta.
- Defectos dependientes de datos.
if (x > 0)con x=1 y x=-1 da 100% pero pierde el defecto de límite si debería ser>=. - Defectos de combinación. Dos decisiones independientes podrían interactuar inesperadamente.
- Issues no funcionales. Performance, seguridad y usabilidad son invisibles a la cobertura.
Ejemplo Real: Procesamiento de Pagos
def procesar_pago(monto, metodo, moneda):
if monto <= 0: # D1
raise ValueError("Monto inválido")
if metodo == "tarjeta_credito": # D2
comision = monto * 0.029
elif metodo == "transferencia": # D3
comision = 1.50
else:
raise ValueError("Método no soportado")
if moneda != "USD": # D4
comision += 0.50
return monto + comision
Test set mínimo para 100% cobertura de decisiones:
| TC | monto | método | moneda | Cubre |
|---|---|---|---|---|
| TC1 | -10 | any | any | D1-V |
| TC2 | 100 | tarjeta_credito | USD | D1-F, D2-V, D4-F |
| TC3 | 100 | transferencia | EUR | D1-F, D2-F, D3-V, D4-V |
| TC4 | 100 | paypal | USD | D1-F, D2-F, D3-F |
Herramientas para Medir Cobertura
| Lenguaje | Herramienta | Comando |
|---|---|---|
| Python | coverage.py | coverage run -m pytest && coverage report |
| JavaScript | Istanbul/nyc | nyc mocha |
| Java | JaCoCo | Integrado con Maven/Gradle |
| Go | Integrado | go test -cover |
Ejercicio: Diseña Tests para Cobertura
Escenario: Analiza esta función y diseña el set mínimo de tests para 100% cobertura de decisiones:
def validar_password(password):
if len(password) < 8:
return {"valido": False, "error": "Muy corto"}
tiene_mayuscula = any(c.isupper() for c in password)
tiene_digito = any(c.isdigit() for c in password)
if not tiene_mayuscula:
return {"valido": False, "error": "Necesita mayúscula"}
if not tiene_digito:
return {"valido": False, "error": "Necesita dígito"}
if len(password) > 64:
return {"valido": False, "error": "Muy largo"}
return {"valido": True, "error": None}
Tareas:
- Identificar todas las decisiones y sus resultados
- Diseñar el set mínimo de tests para 100% cobertura de decisiones
- Calcular cobertura de sentencias para tu set
Pista
Hay 4 puntos de decisión (4 sentencias if), cada uno con resultados True y False = 8 resultados totales. Los retornos tempranos significan que necesitas pensar cuáles decisiones son alcanzables.
Solución
Decisiones: D1: len < 8 (V/F), D2: not mayúscula (V/F), D3: not dígito (V/F), D4: len > 64 (V/F)
Set mínimo (5 test cases):
| TC | Input | D1 | D2 | D3 | D4 | Return |
|---|---|---|---|---|---|---|
| TC1 | "corto" | V | - | - | - | Muy corto |
| TC2 | "todominuscula1" | F | V | - | - | Necesita mayúscula |
| TC3 | "Sindigitos" | F | F | V | - | Necesita dígito |
| TC4 | "A" + "a"*64 + "1" | F | F | F | V | Muy largo |
| TC5 | "ValidPass1" | F | F | F | F | Válido |
Cobertura de sentencias: 100% Cobertura de decisiones: 100%
Tips de Profesional
- Usa la cobertura como guía, no como objetivo. 100% cobertura no significa 100% calidad. Pero baja cobertura definitivamente significa baja confianza.
- Enfócate en cobertura de decisiones sobre sentencias. Es un criterio más fuerte que cuesta poco esfuerzo extra.
- Investiga branches no cubiertos. A veces es código muerto que debería eliminarse. Otras veces es manejo crítico de errores que necesita tests.
- Combina con técnicas de caja negra. La cobertura te dice qué código se ejecutó; EP/BVA te dice qué valores probar.
- Establece objetivos realistas. 80% de cobertura es práctico para la mayoría de proyectos.