El pairwise testing (también conocido como all-pairs testing) es una técnica de testing combinatorial que reduce drásticamente el tamaño de la suite de pruebas mientras mantiene altas tasas de detección de defectos. Al probar todas las combinaciones posibles de pares de parámetros en lugar de todas las combinaciones posibles de todos los parámetros, el pairwise testing logra aproximadamente 90% de cobertura de defectos con una fracción de los casos de prueba.
El Problema de Explosión Combinatoria
Los sistemas de software modernos tienen numerosas opciones de configuración y parámetros de entrada. Probar todas las combinaciones rápidamente se vuelve inviable.
Ejemplo: Testing de Configuración
Considere un checkout e-commerce simple con 5 parámetros:
- Método de Pago: Tarjeta Crédito, PayPal, Transferencia Bancaria (3 opciones)
- Envío: Estándar, Express, Nocturno (3 opciones)
- Envoltorio Regalo: Sí, No (2 opciones)
- Cupón: Aplicado, Ninguno (2 opciones)
- Tipo Usuario: Invitado, Registrado (2 opciones)
Testing exhaustivo: 3 × 3 × 2 × 2 × 2 = 72 casos de prueba
Pairwise testing: Aproximadamente 12-15 casos de prueba cubriendo todas las interacciones pairwise
A medida que los parámetros aumentan, el ahorro se vuelve dramático:
Parámetros | Opciones Cada | Exhaustivo | Pairwise | Reducción |
---|---|---|---|---|
5 | 3 | 243 | ~15 | 94% |
10 | 3 | 59,049 | ~20 | 99.97% |
20 | 2 | 1,048,576 | ~10 | 99.999% |
El Algoritmo All-Pairs
Pairwise testing asegura que para cada par de parámetros, todas las combinaciones de sus valores aparecen en al menos un caso de prueba.
Fundamento Matemático
Investigación de NIST muestra que la mayoría de defectos de software son desencadenados por interacciones de como máximo 2 parámetros (aproximadamente 70% de defectos). Agregar interacciones 3-way atrapa ~90-95% de defectos.
Cobertura Pairwise: Para cada par (P1, P2) de parámetros y cada combinación (v1, v2) donde v1 es un valor de P1 y v2 es un valor de P2, existe al menos un caso de prueba donde P1 = v1 y P2 = v2.
Ejemplo Simple
Parámetros:
- Browser: Chrome, Firefox (2 valores)
- OS: Windows, Mac, Linux (3 valores)
- Network: WiFi, 4G (2 valores)
Exhaustivo: 2 × 3 × 2 = 12 pruebas
Conjunto pairwise:
Test | Browser | OS | Network |
---|---|---|---|
1 | Chrome | Windows | WiFi |
2 | Chrome | Mac | 4G |
3 | Chrome | Linux | WiFi |
4 | Firefox | Windows | 4G |
5 | Firefox | Mac | WiFi |
6 | Firefox | Linux | 4G |
6 pruebas cubren todas las combinaciones pairwise en lugar de 12 pruebas exhaustivas.
Herramientas de Pairwise Testing
PICT (Pairwise Independent Combinatorial Testing)
PICT de Microsoft es la herramienta de pairwise testing más ampliamente usada.
Uso Básico
# Crear archivo modelo: config.txt
Browser: Chrome, Firefox, Edge
OS: Windows, Mac, Linux
Network: WiFi, 4G, Ethernet
# Generar casos de prueba pairwise
pict config.txt
Salida:
Browser OS Network
Chrome Windows WiFi
Chrome Mac 4G
Chrome Linux Ethernet
Firefox Windows Ethernet
Firefox Mac WiFi
Firefox Linux 4G
Edge Windows 4G
Edge Mac Ethernet
Edge Linux WiFi
Features Avanzadas de PICT
Constraints: Excluir combinaciones inválidas
# model.txt
OS: Windows, Mac, Linux
Browser: IE, Safari, Chrome, Firefox
Resolution: 1024x768, 1920x1080, 2560x1440
# Restricciones
IF [OS] = "Mac" THEN [Browser] <> "IE";
IF [OS] = "Windows" THEN [Browser] <> "Safari";
IF [Resolution] = "2560x1440" THEN [OS] <> "Windows";
Submodels: Aumentar fuerza de interacción para parámetros específicos
# Probar todas las interacciones 3-way para parámetros críticos
Payment: CreditCard, PayPal, Crypto
Currency: USD, EUR, GBP
Amount: Low, Medium, High
{ Payment, Currency, Amount } @ 3
Seeding: Incluir casos de prueba específicos obligatorios
Browser: Chrome, Firefox, Safari
OS: Windows, Mac, Linux
# Sembrar combinaciones específicas
Browser OS
Chrome Windows
Firefox Linux
AllPairs (Python)
Implementación Python pura para generación programática de pruebas.
from allpairspy import AllPairs
parameters = [
["Windows", "Mac", "Linux"], # OS
["Chrome", "Firefox", "Safari"], # Browser
["WiFi", "Ethernet", "4G"] # Network
]
# Generar combinaciones pairwise
for i, test in enumerate(AllPairs(parameters)):
print(f"Test {i+1}: OS={test[0]}, Browser={test[1]}, Network={test[2]}")
Salida:
Test 1: OS=Windows, Browser=Chrome, Network=WiFi
Test 2: OS=Windows, Browser=Firefox, Network=Ethernet
Test 3: OS=Windows, Browser=Safari, Network=4G
Test 4: OS=Mac, Browser=Chrome, Network=Ethernet
Test 5: OS=Mac, Browser=Firefox, Network=4G
Test 6: OS=Mac, Browser=Safari, Network=WiFi
Test 7: OS=Linux, Browser=Chrome, Network=4G
Test 8: OS=Linux, Browser=Firefox, Network=WiFi
Test 9: OS=Linux, Browser=Safari, Network=Ethernet
TestCoverageOptimizer (Java)
import com.pairwise.TCO;
public class PairwiseExample {
public static void main(String[] args) {
// Definir parámetros
String[][] parameters = {
{"Windows", "Mac", "Linux"},
{"Chrome", "Firefox", "Edge"},
{"WiFi", "Ethernet"}
};
// Generar pruebas pairwise
TCO tco = new TCO(parameters);
List<int[]> tests = tco.generatePairwise();
// Imprimir casos de prueba
for (int[] test : tests) {
System.out.println(Arrays.toString(test));
}
}
}
CTE (Classification Tree Editor)
Herramienta GUI para modelar y generar pruebas combinatoriales con árboles de clasificación visuales.
Features:
- Modelado de parámetros basado en árbol visual
- Especificación de restricciones
- Múltiples criterios de cobertura (pairwise, 3-way, etc.)
- Exportar casos de prueba a varios formatos
Arreglos Ortogonales
Los arreglos ortogonales son estructuras matemáticas que garantizan cobertura pairwise.
¿Qué son los Arreglos Ortogonales?
Un arreglo ortogonal OA(N, k, v, t) es una matriz N × k donde:
- N = número de casos de prueba
- k = número de parámetros
- v = número de valores por parámetro
- t = fuerza de interacción (2 para pairwise)
Propiedad: Cada submatriz N × t contiene todos los t-tuplas posibles el mismo número de veces.
Ejemplo: Arreglo L9(3^4)
Arreglo ortogonal clásico para 4 parámetros con 3 valores cada uno:
Test | P1 | P2 | P3 | P4 |
---|---|---|---|---|
1 | 1 | 1 | 1 | 1 |
2 | 1 | 2 | 2 | 2 |
3 | 1 | 3 | 3 | 3 |
4 | 2 | 1 | 2 | 3 |
5 | 2 | 2 | 3 | 1 |
6 | 2 | 3 | 1 | 2 |
7 | 3 | 1 | 3 | 2 |
8 | 3 | 2 | 1 | 3 |
9 | 3 | 3 | 2 | 1 |
Exhaustivo requeriría 3^4 = 81 pruebas; arreglo ortogonal necesita solo 9.
Aplicando Arreglos Ortogonales
# Mapear valores a arreglo ortogonal
parameters = {
'Browser': ['Chrome', 'Firefox', 'Edge'],
'OS': ['Windows', 'Mac', 'Linux'],
'Network': ['WiFi', '4G', 'Ethernet'],
'Theme': ['Light', 'Dark', 'Auto']
}
# Usar estructura arreglo L9
L9_array = [
[0, 0, 0, 0],
[0, 1, 1, 1],
[0, 2, 2, 2],
[1, 0, 1, 2],
[1, 1, 2, 0],
[1, 2, 0, 1],
[2, 0, 2, 1],
[2, 1, 0, 2],
[2, 2, 1, 0]
]
# Mapear a valores reales
param_names = ['Browser', 'OS', 'Network', 'Theme']
param_values = [
['Chrome', 'Firefox', 'Edge'],
['Windows', 'Mac', 'Linux'],
['WiFi', '4G', 'Ethernet'],
['Light', 'Dark', 'Auto']
]
tests = []
for row in L9_array:
test = {}
for i, param in enumerate(param_names):
test[param] = param_values[i][row[i]]
tests.append(test)
N-Way Testing: Más Allá de Pairwise
Mientras pairwise (2-way) es lo más común, interacciones de orden superior pueden ser necesarias para sistemas críticos.
Comparación Fuerza de Interacción
Fuerza | Nombre | Cobertura | Casos Prueba | Caso de Uso |
---|---|---|---|---|
1-way | Each Choice | ~50% | Mínimo | Smoke tests |
2-way | Pairwise | ~70-90% | Pequeño | Mayoría sistemas |
3-way | Three-way | ~90-95% | Mediano | Sistemas críticos |
4-way | Four-way | ~95-99% | Grande | Safety-critical |
All | Exhaustivo | 100% | Exponencial | Raro |
Ejemplo 3-Way con PICT
# model.txt
Database: MySQL, PostgreSQL, Oracle
Cache: Redis, Memcached
LoadBalancer: Nginx, HAProxy
# Generar interacciones 3-way
pict model.txt /o:3
Resultado: Todas las combinaciones de 3 parámetros cubiertas (en lugar de solo pares).
Restricciones y Combinaciones Inválidas
Los sistemas reales tienen combinaciones inválidas que deben ser excluidas.
Sintaxis Constraint de PICT
# Modelo e-commerce
PaymentMethod: CreditCard, PayPal, BankTransfer, Cash
ShippingSpeed: Standard, Express, Overnight
Country: USA, Canada, Mexico, UK
Amount: $10, $100, $1000
# Restricciones
IF [PaymentMethod] = "Cash" THEN [ShippingSpeed] = "Standard";
IF [Country] = "UK" THEN [PaymentMethod] <> "Cash";
IF [Amount] = "$1000" THEN [PaymentMethod] <> "Cash";
IF [ShippingSpeed] = "Overnight" THEN [Country] <> "Mexico";
Función Filter de AllPairs
from allpairspy import AllPairs
def is_valid_combination(values, names):
payment, shipping, country = values
# Cash solo con envío estándar
if payment == "Cash" and shipping != "Standard":
return False
# UK no acepta efectivo
if country == "UK" and payment == "Cash":
return False
return True
parameters = [
["CreditCard", "PayPal", "Cash"],
["Standard", "Express", "Overnight"],
["USA", "UK", "Mexico"]
]
for test in AllPairs(parameters, filter_func=is_valid_combination):
print(test)
Flujo de Trabajo de Implementación Práctica
Paso 1: Identificar Parámetros y Valores
Analizar sistema bajo prueba y listar todos los parámetros variables.
# parameters.yaml
authentication:
- username_password
- oauth
- saml
- api_key
database_backend:
- mysql
- postgresql
- sqlite
caching:
- enabled
- disabled
logging_level:
- debug
- info
- warning
- error
Paso 2: Definir Restricciones
Documentar combinaciones inválidas basadas en reglas de negocio y limitaciones técnicas.
# constraints.py
def validate_config(auth, db, cache, log_level):
# SQLite no soporta ciertos métodos auth
if db == "sqlite" and auth == "saml":
return False
# Debug logging requiere caching deshabilitado para precisión
if log_level == "debug" and cache == "enabled":
return False
return True
Paso 3: Generar Suite de Pruebas
Usar PICT o AllPairs para generar conjunto de pruebas optimizado.
# Convertir a formato PICT
authentication: username_password, oauth, saml, api_key
database_backend: mysql, postgresql, sqlite
caching: enabled, disabled
logging_level: debug, info, warning, error
IF [database_backend] = "sqlite" THEN [authentication] <> "saml";
IF [logging_level] = "debug" THEN [caching] = "disabled";
# Generar
pict config.pict > testcases.txt
Paso 4: Ejecutar Pruebas
Mapear combinaciones generadas a pruebas ejecutables.
import pytest
from allpairspy import AllPairs
@pytest.mark.parametrize("auth,db,cache,log", [
test for test in AllPairs([
["username_password", "oauth", "saml", "api_key"],
["mysql", "postgresql", "sqlite"],
["enabled", "disabled"],
["debug", "info", "warning", "error"]
])
])
def test_configuration(auth, db, cache, log):
# Configurar sistema con configuración dada
config = {
'authentication': auth,
'database': db,
'caching': cache,
'logging_level': log
}
system = SystemUnderTest(config)
assert system.health_check()
Beneficios del Pairwise Testing
Reducción Dramática de Suite de Pruebas
Reducir cientos o miles de pruebas a docenas.
Caso de Estudio: Testing de configuración telecom reducido de 1,200 pruebas exhaustivas a 87 pruebas pairwise (93% reducción) sin pérdida en detección de defectos.
Alta Tasa de Detección de Defectos
Investigación muestra que pairwise testing atrapa 70-90% de defectos que testing exhaustivo encontraría.
Estudio NIST: Análisis de fallas de software del mundo real encontró:
- 70% causado por parámetros únicos
- 93% causado por interacciones 2-way
- 98% causado por interacciones 3-way
Ejecución Más Rápida
Menos pruebas significan ciclos de feedback más rápidos.
Ejemplo ROI: Tiempo de ejecución de suite de pruebas de configuración reducido de 8 horas a 45 minutos.
Mejor Mantenimiento de Pruebas
Suites de prueba más pequeñas son más fáciles de mantener y actualizar.
Desafíos y Limitaciones
Identificar Conjuntos Completos de Parámetros
Parámetros faltantes significan interacciones faltantes.
Mitigación: Conducir análisis exhaustivo con expertos de dominio; usar testing exploratorio para suplementar.
Complejidad de Constraints
Reglas de negocio complejas hacen difícil la especificación de restricciones.
Mitigación: Comenzar con restricciones simples; refinar iterativamente basado en fallas de prueba.
Interacciones de Orden Superior
Pairwise no atrapa defectos que requieren interacciones de 3+ parámetros.
Mitigación: Usar testing 3-way para módulos críticos; combinar con testing basado en riesgo.
Aspectos No Funcionales
Pairwise se enfoca en combinaciones funcionales, no en performance o seguridad.
Mitigación: Combinar con testing no funcional dedicado.
Mejores Prácticas
Comenzar con Parámetros Críticos
Enfocar pairwise en features de alto riesgo, frecuentemente usados primero.
# Flujo de pago crítico
PaymentGateway: Stripe, PayPal, Square
Currency: USD, EUR, GBP
PaymentType: OneTime, Subscription
3DS: Enabled, Disabled
# Preferencias UI no críticas probadas exhaustivamente (menos combinaciones)
Theme: Light, Dark
Language: EN, ES
Combinar con Equivalence Partitioning
Usar clases de equivalencia para definir valores de parámetros.
# En lugar de valores específicos
amounts = [0.01, 50.00, 99.99, 100.00, 1000.00, 9999.99]
# Usar clases de equivalencia
amounts = ["Minimum", "Standard", "High", "Maximum"]
# Mapear a valores representativos durante ejecución
Documentar Assumptions
Registrar por qué existen restricciones para mantenimiento futuro.
# model.pict
Browser: Chrome, Firefox, Safari, IE
OS: Windows, Mac, Linux
# IE solo corre en Windows (restricción técnica)
IF [Browser] = "IE" THEN [OS] = "Windows";
# Safari principalmente Mac (decisión de negocio para despriorizar Windows Safari)
IF [Browser] = "Safari" THEN [OS] = "Mac";
Control de Versiones de Modelos de Prueba
Tratar modelos PICT/AllPairs como artefactos de código.
# Estructura repositorio Git
tests/
├── models/
│ ├── authentication.pict
│ ├── checkout.pict
│ └── reporting.pict
├── generated/
│ ├── auth_tests.csv
│ └── checkout_tests.csv
└── scripts/
└── generate_all.sh
Aplicaciones del Mundo Real
Matriz Compatibilidad Browser/OS
Probar aplicación web a través de 5 browsers × 3 OS × 3 tamaños pantalla = 45 pruebas exhaustivas.
Pairwise: 12 pruebas cubriendo todas las interacciones pairwise.
Resultados: Descubrió 8 problemas de rendering, todos atrapados por conjunto pairwise.
Testing Configuración API
API REST con 8 parámetros de configuración, cada uno con 2-4 opciones (1,536 combinaciones exhaustivas).
Pairwise: 24 pruebas.
Resultados: Encontró conflictos de configuración que habrían sido omitidos por muestreo aleatorio.
Testing Fragmentación de Dispositivos
App móvil probada a través de versiones Android, fabricantes, tamaños pantalla, condiciones de red.
Testing 3-way: 150 pruebas (vs. 10,000+ exhaustivas).
Resultados: 95% tasa de detección de defectos con 98.5% reducción en tiempo de ejecución de pruebas.
Conclusión
Pairwise testing proporciona un balance óptimo entre cobertura de pruebas y tamaño de suite de pruebas. Al explotar la observación empírica de que la mayoría de defectos involucran interacciones de pocos parámetros, pairwise testing logra detección de defectos casi exhaustiva con una fracción del esfuerzo.
El éxito con pairwise testing requiere selección cuidadosa de parámetros, modelado reflexivo de restricciones y combinación de pairwise con otras estrategias de testing (exploratorio, basado en riesgo, etc.) para aseguramiento de calidad comprehensivo.