¿Qué Son las Pruebas de Caja Blanca?
Las pruebas de caja blanca (white-box testing) — también llamadas pruebas estructurales, de caja de cristal o caja transparente — son un enfoque de testing donde las pruebas se diseñan basándose en la estructura interna del software. El tester tiene visibilidad completa del código fuente, la arquitectura y los detalles de implementación.
Piensa en ello como inspeccionar el interior de un reloj. En lugar de solo verificar si las manecillas muestran la hora correcta (caja negra), examinas cada engranaje, resorte y mecanismo para verificar que funcionen correctamente.
El white-box testing responde la pregunta: “¿Cada parte del código funciona según lo implementado?”
¿Quién Realiza las Pruebas de Caja Blanca?
Las pruebas de caja blanca las realizan principalmente:
- Desarrolladores durante las pruebas unitarias — conocen el código que escribieron
- SDETs (Software Development Engineers in Test) que pueden leer y analizar código de producción
- QA engineers con habilidades de programación durante pruebas de integración o sistema
Requiere acceso al código fuente y capacidad para comprenderlo. Un tester que no puede leer código no puede realizar white-box testing de manera efectiva.
Técnicas de Cobertura de Código
La cobertura de código (code coverage) mide cuánto del código fuente es ejercitado por tus pruebas. Diferentes criterios de cobertura proporcionan diferentes niveles de exhaustividad.
Cada camino posible] BC[Cobertura de Ramas
Cada resultado de decisión] SC[Cobertura de Sentencias
Cada línea ejecutada] PC -->|incluye| BC BC -->|incluye| SC style PC fill:#ef4444,color:#fff style BC fill:#f97316,color:#000 style SC fill:#22c55e,color:#000
El diagrama muestra la relación de inclusión: lograr cobertura de caminos garantiza cobertura de ramas, que a su vez garantiza cobertura de sentencias. Pero no al revés.
Cobertura de Sentencias (Statement Coverage)
La cobertura de sentencias mide el porcentaje de sentencias ejecutables que han sido ejercitadas por las pruebas.
Fórmula: Cobertura de Sentencias = (Sentencias Ejecutadas / Total Sentencias) × 100%
Considera esta función:
def calculate_discount(price, is_member):
discount = 0 # Sentencia 1
if is_member: # Sentencia 2
discount = price * 0.1 # Sentencia 3
final_price = price - discount # Sentencia 4
return final_price # Sentencia 5
Test 1: calculate_discount(100, True) ejecuta las sentencias 1, 2, 3, 4, 5 → 5/5 = 100% cobertura de sentencias
Pero, ¿qué pasa si hay un bug que solo se manifiesta cuando is_member es False? La cobertura de sentencias no lo detecta porque nunca requirió probar el camino else.
Limitación: 100% de cobertura de sentencias no significa 100% probado. Solo significa que cada línea fue alcanzada al menos una vez.
Cobertura de Ramas (Branch Coverage)
La cobertura de ramas (también llamada cobertura de decisión) mide si cada resultado posible de cada punto de decisión ha sido probado. Cada if, while, for, switch y operador ternario crea ramas.
Fórmula: Cobertura de Ramas = (Ramas Ejecutadas / Total Ramas) × 100%
Usando la misma función:
def calculate_discount(price, is_member):
discount = 0
if is_member: # Rama: True / False
discount = price * 0.1
final_price = price - discount
return final_price
El if is_member crea dos ramas: True y False.
Test 1: calculate_discount(100, True) → ejercita la rama True
Test 2: calculate_discount(100, False) → ejercita la rama False
Ambos tests juntos logran 100% cobertura de ramas (2/2 ramas).
La cobertura de ramas es más fuerte que la de sentencias. Lograr 100% de cobertura de ramas garantiza 100% de cobertura de sentencias, pero no al revés.
Cobertura de Caminos (Path Coverage)
La cobertura de caminos requiere que cada camino posible de ejecución a través de una función sea probado. Un camino es una secuencia única de sentencias desde la entrada hasta la salida.
Para una función con dos sentencias if independientes, hay 4 caminos:
def process_order(is_member, has_coupon):
price = 100
if is_member: # Decisión 1
price -= 10
if has_coupon: # Decisión 2
price -= 5
return price
| Camino | is_member | has_coupon | Resultado |
|---|---|---|---|
| 1 | True | True | 85 |
| 2 | True | False | 90 |
| 3 | False | True | 95 |
| 4 | False | False | 100 |
Con bucles, el número de caminos puede ser infinito (el bucle ejecuta 0, 1, 2 veces, …), haciendo que 100% de cobertura de caminos sea impráctico para la mayoría del código real.
Pruebas de Flujo de Datos
Las pruebas de flujo de datos (data flow testing) rastrean cómo los valores de datos fluyen a través del código. Se enfocan en el ciclo de vida de las variables:
- Definición (def) — donde una variable recibe un valor
- Uso (use) — donde una variable es leída (en un cálculo o decisión)
- Eliminación (kill) — donde una variable queda indefinida o sale de alcance
Criterios comunes de flujo de datos incluyen:
- All-defs: Cada definición de cada variable es alcanzada por al menos un uso
- All-uses: Cada par definición-uso es cubierto
- All-du-paths: Cada camino definición-uso es cubierto
Las pruebas de flujo de datos son efectivas para encontrar errores de inicialización, variables no usadas y referencias colgantes.
Cuándo Usar White-Box Testing
El white-box testing es más efectivo para:
- Pruebas unitarias — probar funciones y métodos individuales
- Refactorización — asegurar que el comportamiento se preserva después de cambios estructurales
- Pruebas de seguridad — encontrar vulnerabilidades en autenticación, encriptación, manejo de entradas
- Optimización — identificar código muerto y caminos inalcanzables
- Cumplimiento regulatorio — industrias como aviación (DO-178C) y automotriz (ISO 26262) requieren niveles específicos de cobertura
Ventajas y Limitaciones
| Ventajas | Limitaciones |
|---|---|
| Prueba cada camino del código, no solo requisitos | No encuentra funcionalidad faltante (solo prueba lo que existe) |
| Encuentra código muerto y ramas inalcanzables | Requiere acceso al código fuente y conocimiento de programación |
| Ayuda a optimizar código revelando complejidad | El mantenimiento de tests es costoso cuando el código cambia frecuentemente |
| Puede automatizarse con herramientas de cobertura | 100% cobertura no garantiza software libre de bugs |
| Detecta errores lógicos invisibles al black-box testing | No valida requisitos del usuario ni usabilidad |
Herramientas de Cobertura por Lenguaje
| Lenguaje | Herramientas Populares |
|---|---|
| Java | JaCoCo, Cobertura, Emma |
| JavaScript/TypeScript | Istanbul/nyc, c8, Vitest coverage |
| Python | Coverage.py, pytest-cov |
| C# | dotCover, OpenCover |
| Go | Built-in go test -cover |
| Ruby | SimpleCov |
Estas herramientas generan reportes mostrando qué líneas, ramas y funciones fueron ejecutadas durante el testing, a menudo con resaltado visual en el IDE.
Ejercicio: Calcula la Cobertura de Sentencias y Ramas
Dada la siguiente función:
def validate_password(password):
errors = [] # S1
if len(password) < 8: # S2, Rama B1 (T/F)
errors.append("Too short") # S3
if len(password) > 64: # S4, Rama B2 (T/F)
errors.append("Too long") # S5
has_upper = False # S6
has_digit = False # S7
for char in password: # S8, Rama B3 (entra/salta)
if char.isupper(): # S9, Rama B4 (T/F)
has_upper = True # S10
if char.isdigit(): # S11, Rama B5 (T/F)
has_digit = True # S12
if not has_upper: # S13, Rama B6 (T/F)
errors.append("No uppercase") # S14
if not has_digit: # S15, Rama B7 (T/F)
errors.append("No digit") # S16
return errors # S17
Parte 1: Ejecutas los siguientes casos de prueba:
- Test A:
validate_password("Ab1cdefgh")— contraseña válida (8+ caracteres, tiene mayúscula, tiene dígito) - Test B:
validate_password("short")— muy corta, sin mayúscula, sin dígito
Calcula la cobertura de sentencias y la cobertura de ramas logradas por estos dos tests combinados.
Parte 2: ¿Qué casos de prueba adicionales necesitarías para lograr 100% de cobertura de ramas? Enuméralos y explica qué ramas cubren.
Parte 3: ¿Es factible la cobertura de caminos al 100% para esta función? Explica por qué sí o por qué no, y estima el número de caminos únicos.
Pista
Para la Parte 1, traza cada caso de prueba línea por línea y marca qué sentencias se ejecutan y qué resultados de rama ocurren. Recuerda que el cuerpo del `for` se ejecuta una vez por carácter.Para la Parte 2, mira la tabla de cobertura de ramas y encuentra cualquier resultado (True o False) que no fue ejercitado por los Tests A y B.
Solución
Parte 1: Cálculo de Cobertura
Test A: validate_password("Ab1cdefgh")
- S1: ✅, S2: ✅ → B1-False, S4: ✅ → B2-False
- S6: ✅, S7: ✅, S8: ✅ → B3-Entra
- S9: ✅ → B4-True (para ‘A’), B4-False (para otros)
- S10: ✅, S11: ✅ → B5-True (para ‘1’), B5-False (para otros)
- S12: ✅, S13: ✅ → B6-False, S15: ✅ → B7-False, S17: ✅
- No ejecutadas: S3, S5, S14, S16
Test B: validate_password("short")
- S1: ✅, S2: ✅ → B1-True, S3: ✅, S4: ✅ → B2-False
- S6: ✅, S7: ✅, S8: ✅ → B3-Entra
- S9: ✅ → B4-False, S11: ✅ → B5-False
- S13: ✅ → B6-True, S14: ✅, S15: ✅ → B7-True, S16: ✅, S17: ✅
Cobertura de sentencias combinada: Ejecutadas: S1-S4, S6-S9, S10-S17 = 16 sentencias No ejecutada: S5 Cobertura: 16/17 = 94.1%
Cobertura de ramas combinada:
- B1: True ✅, False ✅
- B2: True ❌, False ✅
- B3: Entra ✅, Salta ❌
- B4: True ✅, False ✅
- B5: True ✅, False ✅
- B6: True ✅, False ✅
- B7: True ✅, False ✅
Cubiertas: 11 de 14 Cobertura: 11/14 = 78.6%
Parte 2: Tests adicionales para 100% cobertura de ramas
Test C: validate_password("") — cubre B3-Salta (string vacío, el bucle nunca se ejecuta)
Test D: validate_password("A" * 65) — cubre B2-True (longitud > 64)
Con los Tests C y D: las 14 ramas quedan cubiertas = 100% cobertura de ramas.
Parte 3: Factibilidad de cobertura de caminos
La cobertura de caminos al 100% no es factible porque:
- El bucle
foritera una vez por carácter. Para una contraseña de longitud N, cada carácter puede tomar B4-True o B4-False Y B5-True o B5-False, creando 4 combinaciones por carácter. - Para una contraseña de 8 caracteres: aproximadamente 4^8 = 65,536 caminos solo a través del bucle.
- Esto hace que el testing exhaustivo de caminos sea impráctico. En la práctica, se usa selección de caminos basada en límites.
Estándares de Cobertura en la Industria
Diferentes industrias exigen diferentes niveles de cobertura:
- DO-178C (Aviación): Nivel A (falla catastrófica) requiere MC/DC (Modified Condition/Decision Coverage), que es más estricto que la cobertura de ramas
- ISO 26262 (Automotriz): ASIL D requiere MC/DC; ASIL A requiere cobertura de sentencias
- IEC 62304 (Dispositivos médicos): Software Clase C requiere cobertura de ramas
- Web/móvil general: La mayoría de equipos apuntan a 80% de cobertura de sentencias como base práctica
El punto clave es que la cobertura es una condición necesaria pero no suficiente para la calidad. Alta cobertura significa que probaste mucho código. No significa que lo probaste bien.
Puntos Clave
- El white-box testing diseña pruebas basadas en la estructura interna del código, requiriendo acceso al código fuente
- La cobertura de sentencias asegura que cada línea se ejecute; la de ramas que cada resultado de decisión se pruebe; la de caminos que cada ruta de ejecución se pruebe
- Los criterios de cobertura tienen una jerarquía: caminos > ramas > sentencias
- 100% de cobertura no garantiza software libre de bugs — solo significa que el código fue alcanzado
- El white-box testing es esencial para pruebas unitarias, análisis de seguridad y cumplimiento regulatorio
- Las herramientas de cobertura automatizan la medición y reportes en todos los lenguajes principales