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ámetrosOpciones CadaExhaustivoPairwiseReducción
53243~1594%
10359,049~2099.97%
2021,048,576~1099.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:

TestBrowserOSNetwork
1ChromeWindowsWiFi
2ChromeMac4G
3ChromeLinuxWiFi
4FirefoxWindows4G
5FirefoxMacWiFi
6FirefoxLinux4G

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:

TestP1P2P3P4
11111
21222
31333
42123
52231
62312
73132
83213
93321

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

FuerzaNombreCoberturaCasos PruebaCaso de Uso
1-wayEach Choice~50%MínimoSmoke tests
2-wayPairwise~70-90%PequeñoMayoría sistemas
3-wayThree-way~90-95%MedianoSistemas críticos
4-wayFour-way~95-99%GrandeSafety-critical
AllExhaustivo100%ExponencialRaro

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.