Más allá de la cobertura de decisiones
En la lección anterior, aprendiste sobre cobertura de sentencias y decisiones. La cobertura de decisiones asegura que cada branch se ejecute, pero no te dice si las condiciones individuales dentro de una decisión compuesta están realmente probadas. Considera este código:
if sensor_active and temperature > threshold and not emergency_override:
activate_cooling_system()
La cobertura de decisiones solo requiere dos test cases — uno donde toda la expresión sea True y otro donde sea False. Pero ¿cuál condición causó el resultado False? La cobertura de decisiones no se preocupa por eso. Para un sistema de enfriamiento en una planta nuclear, esa distinción es crítica.
Aquí es donde entran los criterios de cobertura a nivel de condición, culminando en MC/DC — la medida de cobertura práctica más rigurosa usada en industrias safety-critical.
Cobertura de condiciones (Condition Coverage)
La cobertura de condiciones requiere que cada condición individual en una decisión tome ambos valores True y False a lo largo del test suite.
Para la decisión A AND B:
| Test | A | B | Decisión |
|---|---|---|---|
| 1 | T | F | F |
| 2 | F | T | F |
Esto logra 100% de cobertura de condiciones — tanto A como B toman valores True y False. Pero observa: el resultado de la decisión es False en ambos casos. Nunca probamos el branch True. La cobertura de condiciones por sí sola puede omitir branches completos.
Cobertura Branch/Condition
La cobertura branch/condition combina ambos requisitos: cada condición debe tomar ambos valores Y cada branch debe ejecutarse.
Para A AND B:
| Test | A | B | Decisión |
|---|---|---|---|
| 1 | T | T | T |
| 2 | F | F | F |
Ahora cubrimos ambos branches y ambas condiciones toman True y False. Pero todavía hay una brecha — no podemos determinar si A afecta independientemente el resultado, porque A y B siempre cambian juntas.
Cobertura de condiciones múltiples
La cobertura de condiciones múltiples (MCC) requiere probar cada combinación posible de valores de condiciones. Para A AND B, eso significa:
| Test | A | B | Decisión |
|---|---|---|---|
| 1 | T | T | T |
| 2 | T | F | F |
| 3 | F | T | F |
| 4 | F | F | F |
Esto es exhaustivo pero impráctico para código real. Con N condiciones, necesitas 2^N test cases. Una decisión con 10 condiciones requiere 1,024 tests. Para 20 condiciones: más de un millón.
Modified Condition/Decision Coverage (MC/DC)
MC/DC es el punto intermedio práctico. Fue desarrollado por NASA y Boeing en los años 90 para software de aviónica y se convirtió en el criterio de cobertura requerido para software de aviación safety-critical bajo DO-178B (ahora DO-178C).
MC/DC requiere:
- Cada punto de entrada y salida se invoca (cobertura de bloques básicos)
- Cada condición toma ambos valores True y False (cobertura de condiciones)
- Cada decisión toma ambos valores True y False (cobertura de decisiones)
- Cada condición afecta independientemente el resultado de la decisión — para cada condición, existe un par de test cases donde solo esa condición cambia de valor y el resultado de la decisión cambia
La regla 4 es lo que hace especial a MC/DC. Demuestra que cada condición realmente importa.
Derivando test cases para MC/DC
Para la decisión A AND B AND C, sigue este proceso sistemático:
Paso 1: Construye la tabla de verdad completa.
| # | A | B | C | Resultado |
|---|---|---|---|---|
| 1 | T | T | T | T |
| 2 | T | T | F | F |
| 3 | T | F | T | F |
| 4 | T | F | F | F |
| 5 | F | T | T | F |
| 6 | F | T | F | F |
| 7 | F | T | F | F |
| 8 | F | F | F | F |
Paso 2: Encuentra pares de independencia para cada condición. Un par de independencia para la condición X son dos filas donde X cambia, todas las demás condiciones permanecen iguales, y el resultado de la decisión cambia.
- Condición A: Filas 1 (T,T,T→T) y 5 (F,T,T→F) — A cambia, B y C se mantienen en T, resultado cambia
- Condición B: Filas 1 (T,T,T→T) y 3 (T,F,T→F) — B cambia, A y C se mantienen en T, resultado cambia
- Condición C: Filas 1 (T,T,T→T) y 2 (T,T,F→F) — C cambia, A y B se mantienen en T, resultado cambia
Paso 3: Selecciona el conjunto mínimo cubriendo todos los pares: filas {1, 2, 3, 5} = 4 test cases.
Para N condiciones con AND: N+1 test cases. Para N condiciones con OR: también N+1. Esto es dramáticamente mejor que los 2^N requeridos por cobertura de condiciones múltiples.
MC/DC para operadores mixtos
Cuando una decisión mezcla AND y OR, el proceso es el mismo pero encontrar pares de independencia requiere más cuidado.
Para (A OR B) AND C:
| # | A | B | C | A OR B | Resultado |
|---|---|---|---|---|---|
| 1 | T | T | T | T | T |
| 2 | T | T | F | T | F |
| 3 | T | F | T | T | T |
| 4 | T | F | F | T | F |
| 5 | F | T | T | T | T |
| 6 | F | T | F | T | F |
| 7 | F | F | T | F | F |
| 8 | F | F | F | F | F |
Pares de independencia:
- A: Filas 3 (T,F,T→T) y 7 (F,F,T→F)
- B: Filas 5 (F,T,T→T) y 7 (F,F,T→F)
- C: Filas 1 (T,T,T→T) y 2 (T,T,F→F)
Conjunto mínimo: {1, 2, 3, 5, 7} = 5 test cases. Operadores mixtos pueden requerir ligeramente más que N+1.
Dónde se requiere MC/DC
MC/DC no es solo un ejercicio académico. Es mandatorio según estándares reales:
| Estándar | Dominio | MC/DC requerido para |
|---|---|---|
| DO-178C | Aviación | Software Level A (falla catastrófica) |
| ISO 26262 | Automotriz | Software ASIL D (riesgo más alto) |
| IEC 61508 | Industrial | SIL 4 (mayor integridad de seguridad) |
| EN 50128 | Ferroviario | Software SIL 3 y SIL 4 |
Si trabajas en cualquiera de estas industrias, MC/DC no es opcional — es un requisito regulatorio.
Herramientas para análisis MC/DC
Varias herramientas soportan medición MC/DC:
- VectorCAST — Estándar de la industria para sistemas embedded y safety-critical
- LDRA — Ampliamente usado en aeroespacial y automotriz
- Parasoft C/C++test — Soporta MC/DC para codebases C y C++
- gcov + análisis custom — Opción open-source para tracking básico de condiciones
- JaCoCo — Herramienta de cobertura Java que soporta cobertura de branches (no MC/DC completo, pero útil como aproximación)
Ejercicio: Derivar test cases MC/DC
Problema 1
Dada la función:
def should_trigger_alert(pressure_high, temp_critical, manual_override):
return (pressure_high or temp_critical) and not manual_override
Deriva el conjunto mínimo de tests para MC/DC.
Solución
Construye la tabla de verdad para (A OR B) AND NOT C donde A=pressure_high, B=temp_critical, C=manual_override:
| # | A | B | C | NOT C | A OR B | Resultado |
|---|---|---|---|---|---|---|
| 1 | T | T | F | T | T | T |
| 2 | T | F | F | T | T | T |
| 3 | F | T | F | T | T | T |
| 4 | F | F | F | T | F | F |
| 5 | T | T | T | F | T | F |
| 6 | T | F | T | F | T | F |
| 7 | F | T | T | F | T | F |
| 8 | F | F | T | F | F | F |
Pares de independencia:
- A (pressure_high): Filas 2 (T,F,F→T) y 4 (F,F,F→F)
- B (temp_critical): Filas 3 (F,T,F→T) y 4 (F,F,F→F)
- C (manual_override): Filas 2 (T,F,F→T) y 6 (T,F,T→F)
Conjunto mínimo: {2, 3, 4, 6} = 4 test cases.
| Test | pressure_high | temp_critical | manual_override | Esperado |
|---|---|---|---|---|
| 1 | True | False | False | True |
| 2 | False | True | False | True |
| 3 | False | False | False | False |
| 4 | True | False | True | False |
Problema 2
Un sistema de control de vuelo tiene esta lógica:
if (altitude_low && airspeed_low && !landing_gear_deployed) {
trigger_terrain_warning();
}
- ¿Cuántos test cases requiere MC/DC?
- ¿Cuántos requiere cobertura de condiciones múltiples?
- Deriva el conjunto de tests MC/DC.
Solución
Decisión: A AND B AND NOT C donde A=altitude_low, B=airspeed_low, C=landing_gear_deployed.
- MC/DC requiere 4 test cases (N+1 = 3+1, ya que NOT C sigue siendo una condición)
- Cobertura de condiciones múltiples requiere 8 test cases (2^3)
Tabla de verdad:
| # | A | B | C | NOT C | Resultado |
|---|---|---|---|---|---|
| 1 | T | T | F | T | T |
| 2 | T | T | T | F | F |
| 3 | T | F | F | T | F |
| 4 | F | T | F | T | F |
Pares de independencia:
- A: Filas 1 y 4 (A cambia T→F, B=T, C=F fijos, resultado T→F)
- B: Filas 1 y 3 (B cambia T→F, A=T, C=F fijos, resultado T→F)
- C: Filas 1 y 2 (C cambia F→T, A=T, B=T fijos, resultado T→F)
Conjunto mínimo MC/DC: {1, 2, 3, 4} — exactamente 4 tests en lugar de 8.
Tips prácticos para MC/DC
Simplifica antes de analizar. Si una condición contiene sub-expresiones redundantes, simplifica usando álgebra booleana primero. MC/DC en una expresión simplificada requiere menos tests.
Condiciones acopladas vs. desacopladas. Cuando dos condiciones comparten una variable (por ejemplo, x > 5 AND x < 10), no puedes variar una independientemente de la otra. Esto se llama condición acoplada. Las condiciones acopladas pueden requerir pares de independencia diferentes a las desacopladas.
Usa masking MC/DC para expresiones complejas. En masking MC/DC (permitido por DO-178C), se puede demostrar que una condición afecta independientemente la decisión incluso si otras condiciones cambian, siempre que los cambios estén “enmascarados” (no afecten el resultado a través de la estructura lógica). Esto puede reducir aún más el conjunto de tests requerido.
La evaluación de cortocircuito importa. En lenguajes con evaluación de cortocircuito (la mayoría de los lenguajes modernos), A AND B no evalúa B si A es False. Esto significa que algunos test cases MC/DC pueden no ejercitar ciertas condiciones en runtime. Considera si tu herramienta de cobertura tiene en cuenta el comportamiento de cortocircuito.
Puntos clave
- La cobertura de condiciones prueba condiciones individuales pero puede omitir branches completos
- La cobertura branch/condition combina ambas pero no demuestra independencia
- MC/DC demuestra que cada condición afecta independientemente el resultado de la decisión
- MC/DC requiere N+1 test cases (vs. 2^N para cobertura de condiciones múltiples) — práctico incluso para decisiones complejas
- MC/DC es mandatorio por DO-178C, ISO 26262 e IEC 61508 para software safety-critical
- Siempre encuentra pares de independencia sistemáticamente usando tablas de verdad
- Considera condiciones acopladas, evaluación de cortocircuito y masking MC/DC para decisiones complejas del mundo real