El Model-Based Testing (MBT) representa un cambio de paradigma en la automatización de pruebas, donde las pruebas se generan automáticamente desde modelos abstractos del comportamiento del sistema en lugar de escribirse manualmente. Este enfoque permite a los testers enfocarse en modelar el comportamiento esperado mientras las herramientas se encargan del trabajo mecánico de generación y ejecución de casos de prueba.

¿Qué es Model-Based Testing?

Model-Based Testing utiliza modelos formales o semiformales para representar el comportamiento esperado de un sistema bajo prueba. Estos modelos—como máquinas de estado, diagramas UML o tablas de decisión—sirven como fuente única de verdad desde la cual se derivan automáticamente casos de prueba, datos de prueba y resultados esperados.

Conceptos Clave

Modelo: Representación abstracta del comportamiento del sistema que captura estados, transiciones, entradas y salidas esperadas.

Algoritmo de Generación de Pruebas: Lógica que recorre el modelo para crear caminos de prueba que cumplan criterios específicos de cobertura.

Criterios de Cobertura: Reglas que definen qué tan exhaustivamente debe explorarse el modelo (ej. todos los estados, todas las transiciones, todos los caminos).

Oráculo: Mecanismo para determinar resultados esperados, derivado directamente de la especificación del modelo.

Tipos de Modelos en MBT

Modelos de Máquinas de Estado

Las máquinas de estado son excelentes para modelar sistemas con modos operacionales distintos y transiciones de estado.

# Ejemplo: Modelo de máquina de estado para sistema de login
from graphwalker import Model, Edge, Vertex

class LoginModel(Model):
    def __init__(self):
        super().__init__()

        # Definir vértices (estados)
        logged_out = Vertex("LoggedOut")
        login_page = Vertex("LoginPage")
        logged_in = Vertex("LoggedIn")
        locked_out = Vertex("LockedOut")

        # Definir aristas (transiciones)
        self.add_edge(Edge(logged_out, login_page, "navigateToLogin"))
        self.add_edge(Edge(login_page, logged_in, "loginSuccess"))
        self.add_edge(Edge(login_page, login_page, "loginFailure"))
        self.add_edge(Edge(login_page, locked_out, "threeFailedAttempts"))
        self.add_edge(Edge(logged_in, logged_out, "logout"))

        self.start_vertex = logged_out

Diagramas de Actividad UML

Los diagramas de actividad modelan flujos de trabajo y procesos de negocio, ideales para procedimientos complejos de múltiples pasos.

Ventajas:

  • Representación visual del flujo del proceso
  • Captura puntos de decisión y actividades paralelas
  • Notación familiar para stakeholders
  • Soporta swimlanes para escenarios multi-actor

Tablas de Decisión

Las tablas de decisión representan de forma compacta lógica condicional compleja con múltiples combinaciones de entrada.

Intentos LoginPassword VálidoEstado CuentaResultado
≤ 3ActivaÉxito
≤ 3NoActivaFallo
> 3CualquieraActivaBloqueo
CualquieraCualquieraBloqueadaError Bloqueado
CualquieraInactivaError Inactivo

Herramientas y Frameworks MBT

GraphWalker (Java/Python)

GraphWalker genera secuencias de prueba caminando a través de grafos dirigidos que representan el comportamiento del sistema.

// Definición de modelo GraphWalker
public class ShoppingCartModel implements Model {

    @Action
    public void e_AddItem() {
        // Acción: Agregar ítem al carrito
        cart.addItem(testProduct);
    }

    @Action
    public void e_RemoveItem() {
        // Acción: Remover ítem del carrito
        cart.removeItem(testProduct);
    }

    @Action
    public void e_Checkout() {
        // Acción: Proceder al checkout
        cart.checkout();
    }

    @State
    public void v_EmptyCart() {
        // Verificar: Carrito vacío
        assertTrue(cart.isEmpty());
    }

    @State
    public void v_CartWithItems() {
        // Verificar: Carrito contiene ítems
        assertFalse(cart.isEmpty());
    }

    @State
    public void v_CheckoutPage() {
        // Verificar: En página de checkout
        assertTrue(page.isCheckoutPage());
    }
}

ModelJUnit (Java)

ModelJUnit usa código Java para especificar modelos y genera pruebas usando algoritmos aleatorios o greedy.

public class ElevatorModel implements FsmModel {
    private int floor = 0;
    private int target = 0;
    private boolean doorsOpen = false;

    public boolean selectFloorGuard() {
        return !doorsOpen;
    }

    @Action
    public void selectFloor() {
        target = random.nextInt(10);
    }

    @Action
    public void openDoors() {
        doorsOpen = true;
    }

    @Action
    public void closeDoors() {
        doorsOpen = false;
    }

    public boolean moveUpGuard() {
        return floor < target && !doorsOpen;
    }

    @Action
    public void moveUp() {
        floor++;
    }

    public boolean moveDownGuard() {
        return floor > target && !doorsOpen;
    }

    @Action
    public void moveDown() {
        floor--;
    }
}

Spec Explorer (Microsoft)

Spec Explorer se especializa en testing de protocolos y validación de APIs usando Cord (C# para modelado).

Características Clave:

  • Programas de modelo escritos en C#
  • Estrategias de exploración para espacio de estados
  • Slicing y filtrado de casos de prueba
  • Integración con Visual Studio

Criterios de Cobertura

Diferentes criterios de cobertura balancean exhaustividad contra tamaño de suite de pruebas.

Cobertura de Estados

Visitar cada estado en el modelo al menos una vez.

Ventaja: Asegura que todos los modos del sistema sean probados Limitación: Puede omitir combinaciones críticas de transiciones

Cobertura de Transiciones

Ejercitar cada transición en el modelo al menos una vez.

Ventaja: Más exhaustiva que cobertura de estados Tamaño de Suite: Moderado

Cobertura de Caminos

Ejecutar todos los caminos posibles a través del modelo.

Ventaja: Testing comprehensivo Limitación: A menudo inviable debido a explosión combinatoria

Criterios Prácticos Comunes

CriterioObjetivo de CoberturaTamaño de SuiteCaso de Uso
Cobertura EstadosTodos los estadosPequeñoSmoke testing
Cobertura TransicionesTodas las transicionesMedianoRegresión estándar
Transiciones 2-WayTodos los pares transiciónGrandeTesting exhaustivo
Cobertura Edge-PairTodos los pares aristaGrandeSistemas críticos
Todos los CaminosTodos los caminos posiblesExponencialSolo modelos pequeños

Flujo de Trabajo MBT

Paso 1: Creación del Modelo

Analizar requisitos y crear un modelo abstracto que capture el comportamiento del sistema.

# Ejemplo: Modelo ATM en Python
class ATMModel:
    def __init__(self):
        self.state = "idle"
        self.balance = 1000
        self.pin_attempts = 0

    def insert_card(self):
        if self.state == "idle":
            self.state = "card_inserted"
            return True
        return False

    def enter_pin(self, correct):
        if self.state == "card_inserted":
            if correct:
                self.state = "authenticated"
                self.pin_attempts = 0
            else:
                self.pin_attempts += 1
                if self.pin_attempts >= 3:
                    self.state = "card_captured"
            return True
        return False

    def withdraw(self, amount):
        if self.state == "authenticated":
            if amount <= self.balance:
                self.balance -= amount
                return True
        return False

Paso 2: Generación de Pruebas

Configurar criterios de cobertura y generar secuencias de prueba automáticamente.

# Ejemplo CLI GraphWalker
graphwalker offline \
  --model shopping-cart.json \
  --generator "random(edge_coverage(100))" \
  --output tests.json

Paso 3: Ejecución de Pruebas

Ejecutar pruebas generadas contra el sistema real, comparando resultados con predicciones del modelo.

// Ejecutar secuencia de prueba generada
GraphWalker walker = new GraphWalker(new ShoppingCartModel());
walker.setPathGenerator(new RandomPath(new EdgeCoverage(100)));

while (walker.hasNextStep()) {
    walker.getNextStep().execute();
    assertTrue(walker.getCurrentVertex().verify());
}

Paso 4: Análisis y Refinamiento

Analizar fallos para determinar si el defecto está en el sistema o en el modelo.

Beneficios del Model-Based Testing

Mayor Cobertura de Pruebas

La generación automatizada explora caminos que testers humanos podrían pasar por alto.

Caso de Estudio: Compañía telecom usando MBT incrementó cobertura de transiciones de 67% a 98%, descubriendo 12 defectos previamente desconocidos.

Mantenimiento Reducido

Actualiza el modelo una vez; las pruebas se regeneran automáticamente.

Ejemplo ROI: 60% de reducción en esfuerzo de mantenimiento de pruebas cuando los requisitos cambian frecuentemente.

Detección Temprana de Defectos

La creación del modelo fuerza especificación precisa, revelando ambigüedades en requisitos.

Impacto Shift-Left: 40% de defectos encontrados durante fase de modelado, antes de que exista código.

Documentación Viva

Los modelos sirven como especificaciones ejecutables que nunca quedan obsoletas.

Desafíos y Limitaciones

Esfuerzo de Creación del Modelo

Construir modelos precisos requiere inversión inicial significativa.

Mitigación: Comenzar con flujos críticos; expandir cobertura iterativamente.

Curva de Aprendizaje de Herramientas

Las herramientas MBT tienen curvas de aprendizaje más pronunciadas que frameworks basados en scripts.

Mitigación: Invertir en entrenamiento; construir expertise interna gradualmente.

Sistemas No Determinísticos

Sistemas con comportamiento impredecible (ej. dependencias externas pesadas) son más difíciles de modelar.

Mitigación: Modelar en niveles de abstracción más altos; usar stubs para sistemas externos.

Validez del Modelo

Los modelos pueden divergir del comportamiento real del sistema con el tiempo.

Mitigación: Validación continua; checks automatizados de trazabilidad modelo-a-código.

Mejores Prácticas

Comenzar Pequeño

Empezar con un modelo limitado que cubra funcionalidad core.

# Modelo inicial: Solo autenticación core de usuario
class MinimalAuthModel:
    states = ["logged_out", "logged_in"]
    transitions = [
        ("logged_out", "login_success", "logged_in"),
        ("logged_in", "logout", "logged_out")
    ]

Colaborar con Stakeholders

Involucrar expertos de dominio en revisión del modelo para asegurar precisión.

Técnica: Desarrollo de modelo basado en workshops con analistas de negocio y desarrolladores.

Combinar con Testing Tradicional

Usar MBT para lógica compleja; usar pruebas scriptadas para casos simples.

Estrategia Híbrida: 70% MBT para features dependientes de estado, 30% scriptado para validación UI.

Mantener Trazabilidad del Modelo

Vincular elementos del modelo a requisitos para análisis de impacto.

# Mapeo de trazabilidad
transitions:
  - id: T1
    name: "login_success"
    requirements: [REQ-AUTH-001, REQ-AUTH-003]
  - id: T2
    name: "login_failure"
    requirements: [REQ-AUTH-002]

Control de Versiones de Modelos

Tratar modelos como artefactos de primera clase en control de versiones.

# Estructura Git para MBT
models/
  ├── authentication.graphml
  ├── shopping-cart.json
  └── payment-flow.yaml
tests/
  ├── generated/
  └── manual/

Aplicaciones del Mundo Real

Testing de Sistemas Embebidos

Fabricante automotriz usa MBT para testing de ECU, generando miles de casos de prueba desde máquinas de estado.

Resultados: 35% reducción en defectos escapados, 50% más rápido testing de regresión.

Testing de Aplicaciones Web

Plataforma e-commerce modela flujos de checkout con 15 estados y 40 transiciones.

Resultados: Descubrió 8 casos edge no cubiertos por pruebas manuales.

Testing de API

Firma de servicios financieros modela transiciones de estado de API REST para procesamiento de pagos.

Resultados: 100% cobertura de transiciones alcanzada; modelo sirve como documentación de API.

Conclusión

Model-Based Testing cambia el foco del testing de casos de prueba individuales a modelos comprehensivos de comportamiento. Si bien requiere inversión inicial significativa en habilidades de modelado y herramientas, MBT entrega beneficios sustanciales a largo plazo en cobertura, mantenibilidad y detección de defectos—especialmente para sistemas complejos con comportamiento rico dependiente de estado.

El éxito con MBT requiere tratar los modelos como artefactos vivos, manteniendo su precisión mediante refinamiento y validación continua contra el comportamiento del sistema en evolución.