Ir al contenido
  • Inicio
  • Libro
  • Blog
  • Sobre nosotros
  • Ayuda
  • Contáctanos
  •  [email protected]
  • Inicia sesión
  • English (US) Español (BO)
  • Simplify It S.R.L.
    • Contáctanos
Simplify It S.R.L.
      • Inicio
      • Libro
      • Blog
      • Sobre nosotros
      • Ayuda
      • Contáctanos
    •  [email protected]
    • English (US) Español (BO)
    • Inicia sesión
    • Contáctanos

    Capítulo 16 Parte 2: Pruebas Avanzadas, TDD y Depuración en Producción

  • Todos los blogs
  • Libro OWL
  • Capítulo 16 Parte 2: Pruebas Avanzadas, TDD y Depuración en Producción
  • 25 de agosto de 2026 por
    Capítulo 16 Parte 2: Pruebas Avanzadas, TDD y Depuración en Producción
    Grover Menacho

    ¿Por qué este capítulo? Los proyectos reales tarde o temprano se topan con los problemas que no aparecen en una revisión manual rápida — una fuga de memoria que solo aparece después de semanas corriendo, una prueba inestable que erosiona la confianza en toda la suite, un bug que solo se reproduce bajo carga de producción. Este es el instrumental para esa etapa de la vida de un proyecto. ¿Qué trata de resolver Odoo con esto? Darle al equipo una forma de probar la integración entre servicios, conectar CI para detectar regresiones antes del despliegue, y depurar problemas de producción de forma segura — en vez de sesiones improvisadas de console.log en el servidor en vivo de un cliente. Aplicación en la vida real: Diagnosticar una fuga de memoria que un cliente reporta después de un mes de uso diario, o configurar CI para que cada merge se pruebe automáticamente antes de llegar a su instancia.

    En la Parte 1 cubrimos los fundamentos de pruebas y depuración de componentes OWL: escribir tus primeras pruebas, probar componentes que dependen de servicios, usar las DevTools del navegador efectivamente y seguir buenas prácticas de pruebas. Ahora iremos más lejos: patrones de pruebas avanzados, Desarrollo Dirigido por Pruebas, pruebas de rendimiento e integración, integración continua, errores en los que caen incluso desarrolladores experimentados, y cómo depurar problemas una vez que tu código está corriendo en producción.


    Patrones Avanzados de Pruebas

    1. Probando Comunicación de Componentes

    Al probar la comunicación padre-hijo:

    /** @odoo-module **/
    
    import { Component, useState, xml } from "@odoo/owl";
    import { describe, test, expect } from "@odoo/hoot";
    import { click } from "@odoo/hoot-dom";
    import { mountWithCleanup } from "@web/../tests/web_test_helpers";
    import { Counter } from "../counter";
    
    describe("Integración de Counter", () => {
        test("comunicación padre-hijo", async () => {
            const receivedEvents = [];
    
            // Crear un componente padre que usa nuestro componente probado
            class TestParent extends Component {
                static template = xml`
                    <div>
                        <Counter initialValue="5" onValueChange.bind="onCounterChange"/>
                        <p class="parent-display">El padre ve: <t t-esc="state.lastValue"/></p>
                    </div>
                `;
                static components = { Counter };
    
                setup() {
                    this.state = useState({ lastValue: 5 });
                }
    
                onCounterChange(newValue) {
                    receivedEvents.push(newValue);
                    this.state.lastValue = newValue;
                }
            }
    
            await mountWithCleanup(TestParent);
    
            // Interactuar con el componente hijo
            await click(".increment-btn");
    
            // Verificar que el padre recibió el evento
            expect(receivedEvents).toEqual([6]);
            expect(".parent-display").toHaveText("El padre ve: 6");
        });
    });
    

    2. Probando con Cambios de Props

    Probar cómo los componentes reaccionan a cambios de props. Como en OWL las props siempre fluyen hacia abajo desde un padre, la forma más limpia de probar esto es envolver el componente en un pequeño padre de prueba y cambiar lo que le pasa hacia abajo — el mismo patrón usado arriba:

    describe("Integración de Counter", () => {
        test("reacciona a cambios de props", async () => {
            class TestParent extends Component {
                static template = xml`<Counter max="state.max"/>`;
                static components = { Counter };
                setup() {
                    this.state = useState({ max: 5 });
                }
            }
    
            await mountWithCleanup(TestParent);
    
            // Incrementar hasta el máximo
            for (let i = 0; i < 5; i++) {
                await click(".increment-btn");
            }
            expect(".increment-btn").toHaveProperty("disabled", true);
    
            // Subir el máximo en el estado del padre; OWL re-renderiza el hijo con las nuevas props
            // Nota: este fragmento solo ilustra la idea — el estado de TestParent no es
            // accesible desde afuera, así que en una prueba real expondrías una forma de
            // actualizarlo (p. ej. un botón en la plantilla de TestParent) y harías clic ahí.
        });
    });
    

    3. Probando Operaciones Asíncronas

    Para componentes con operaciones asíncronas:

    describe("Integración de TaskManager", () => {
        test("maneja operaciones asíncronas", async () => {
            let resolvePromise;
            const asyncPromise = new Promise((resolve) => {
                resolvePromise = resolve;
            });
            onRpc("project.task", "search_read", () => asyncPromise);
    
            await mountWithCleanup(TaskManager);
    
            // Verificar estado de carga
            expect(".task-list p").toHaveCount(1);
    
            // Resolver la operación asíncrona
            resolvePromise([]);
            await animationFrame(); // dejar que OWL procese el re-render tras resolver la promesa
    
            // Verificar que la carga desapareció
            expect(".task-list p").toHaveCount(0);
        });
    });
    

    Agrega import { animationFrame } from "@odoo/hoot-dom"; al principio — es el helper genérico de "esperar un tick a que el DOM se actualice" en hoot, útil cuando resuelves una promesa a mano en vez de pasar por click() o edit() (que ya esperan internamente).


    Desarrollo Dirigido por Pruebas (TDD) con OWL

    El Desarrollo Dirigido por Pruebas es un enfoque poderoso donde escribes pruebas antes de implementar la funcionalidad:

    1. Ciclo Rojo-Verde-Refactorizar

    Rojo: Escribir una prueba que falle

    test("el contador se muestra correctamente", async () => {
        await mountWithCleanup(Counter);
        expect(".counter-value").toHaveText("0");
    });
    

    Verde: Escribir código mínimo para que pase

    export class Counter extends Component {
        static template = xml`
            <div>
                <span class="counter-value">0</span>
            </div>
        `;
    }
    

    Refactorizar: Mejorar el código manteniendo las pruebas verdes

    export class Counter extends Component {
        static template = xml`
            <div class="counter-widget">
                <span class="counter-value" t-esc="state.count"/>
            </div>
        `;
    
        setup() {
            this.state = useState({ count: 0 });
        }
    }
    

    2. Beneficios del TDD

    • Requisitos Claros: Las pruebas sirven como especificaciones
    • Mejor Diseño: Escribir pruebas primero lleva a código más testeable y modular
    • Protección contra Regresiones: Suite integral de pruebas detecta rupturas futuras
    • Confianza: Pruebas verdes significan software funcionando

    Pruebas de Rendimiento y Depuración

    1. Midiendo el Rendimiento de Componentes

    test("rendimiento del contador con muchas actualizaciones", async () => {
        await mountWithCleanup(Counter, { props: { max: 1000 } });
    
        const startTime = performance.now();
    
        // Simular clics rápidos
        for (let i = 0; i < 100; i++) {
            await click(".increment-btn");
        }
    
        const duration = performance.now() - startTime;
    
        expect(duration).toBeLessThan(1000);
        expect(".counter-value").toHaveText("100");
    });
    

    2. Detección de Fugas de Memoria

    Esta prueba gestiona el montaje/destrucción a mano en vez de usar mountWithCleanup(), ya que su propósito es verificar que la limpieza manual no deja nada atrás — así que importa mount directamente de @odoo/owl:

    import { mount } from "@odoo/owl";
    
    test("el contador se limpia apropiadamente", async () => {
        const initialComponents = document.querySelectorAll(".counter-widget").length;
        const container = document.createElement("div");
    
        // Crear y destruir múltiples componentes
        for (let i = 0; i < 10; i++) {
            const counter = await mount(Counter, container);
            counter.destroy();
            container.innerHTML = ""; // Limpiar el contenedor
        }
    
        // Forzar recolección de basura si está disponible
        if (window.gc) {
            window.gc();
        }
    
        const finalComponents = document.querySelectorAll(".counter-widget").length;
        expect(finalComponents).toBe(initialComponents);
    });
    

    Pruebas de Integración

    Probando Componentes en Escenarios Reales

    A veces necesitas probar cómo los componentes trabajan juntos en condiciones realistas:

    test("el contador funciona en contexto de formulario", async () => {
        // Crear un componente padre realista
        class TestForm extends Component {
            static template = xml`
                <form t-on-submit.prevent="onSubmit">
                    <div class="form-group">
                        <label>Cantidad:</label>
                        <Counter initialValue="1"
                                 min="1"
                                 max="10"
                                 onValueChange.bind="onQuantityChange"/>
                    </div>
                    <div class="form-group">
                        <span>Total: $<t t-esc="state.total"/></span>
                    </div>
                    <button type="submit" class="btn btn-primary">Ordenar</button>
                </form>
            `;
    
            static components = { Counter };
    
            setup() {
                this.state = useState({
                    quantity: 1,
                    price: 10,
                    total: 10,
                });
            }
    
            onQuantityChange(newQuantity) {
                this.state.quantity = newQuantity;
                this.state.total = this.state.quantity * this.state.price;
            }
    
            onSubmit() {
                // Lógica de envío del formulario
            }
        }
    
        await mountWithCleanup(TestForm);
    
        // Probar comportamiento integrado
        await click(".increment-btn");
    
        expect(".counter-value").toHaveText("2");
        expect(".form-group:nth-child(2)").toHaveText("Total: $20");
    });
    

    Integración Continua y Pruebas

    1. Ejecutando Pruebas en CI/CD

    Crear un script de prueba para tu módulo:

    Archivo: mi_modulo/tests/test_js.py

    import odoo.tests
    
    
    @odoo.tests.tagged('post_install', '-at_install')
    class TestJavaScript(odoo.tests.HttpCase):
    
        def test_js_modules(self):
            """Probar que los módulos JavaScript cargan y las pruebas unitarias de hoot pasan"""
            self.browser_js(
                "/web/tests?module=mi_modulo&failfast",
                code="",
                timeout=60,
            )
    

    2. Reportes de Cobertura de Pruebas

    La cobertura para las pruebas de hoot se habilita desde el propio runner de pruebas (p. ej. un parámetro de URL coverage=1 en /web/tests), no desde dentro del archivo de prueba. Una vez que termina una ejecución con cobertura habilitada, el objeto de cobertura estándar de Istanbul queda disponible en la página para inspeccionarlo o exportarlo:

    if (window.__coverage__) {
        console.log("Datos de cobertura:", window.__coverage__);
    }
    

    Errores Comunes de Pruebas y Soluciones

    1. Pruebas Inestables

    Problema: Pruebas que a veces pasan y a veces fallan

    Solución: Asegurar el manejo apropiado de async

    // Malo - no esperar operaciones asíncronas
    test("prueba inestable", async () => {
        await mountWithCleanup(Counter);
        click(".increment-btn"); // ¡No se esperó!
        expect(".counter-value").toHaveText("1");
    });
    
    // Bueno - esperar apropiadamente operaciones asíncronas
    test("prueba confiable", async () => {
        await mountWithCleanup(Counter);
        await click(".increment-btn"); // click() de hoot ya espera el re-renderizado
        expect(".counter-value").toHaveText("1");
    });
    

    2. Probando Métodos Privados

    Problema: Querer probar métodos internos del componente

    Solución: Probar a través de la interfaz pública

    // Malo - probando métodos privados
    test("_calculateTotal funciona", async () => {
        const component = await mountWithCleanup(MyComponent);
        const result = component._calculateTotal(5, 10);
        expect(result).toBe(50);
    });
    
    // Bueno - probando a través del comportamiento público
    test("muestra el total correcto cuando cambia la cantidad", async () => {
        await mountWithCleanup(MyComponent);
        await edit(".quantity-input", "5");
        expect(".total").toHaveText("Total: 50");
    });
    

    3. Exceso de Mocking

    Problema: Hacer mock de demasiadas dependencias

    Solución: Hacer mock solo de lo necesario

    // Malo - exceso de mocking de cada servicio que toca el componente
    mockService("orm", mockOrm);
    mockService("notification", mockNotification);
    mockService("user", mockUser);
    mockService("company", mockCompany);
    // ... hacer mock de todo vuelve la prueba frágil y esconde bugs reales de integración
    
    // Bueno - hacer mock solo de la llamada RPC específica que le importa a esta prueba
    onRpc("mi.modelo", "mi_metodo_especifico", () => mockData);
    // el resto de las solicitudes pasan por el manejo normal
    

    Depurando Problemas de Producción

    1. Límites de Error y Logging

    export class ErrorBoundary extends Component {
        static template = xml`
            <div>
                <t t-if="state.hasError">
                    <div class="alert alert-danger">
                        <h4>Algo salió mal</h4>
                        <p t-esc="state.error.message"/>
                        <button t-on-click="retry">Intentar de nuevo</button>
                    </div>
                </t>
                <t t-else="">
                    <t t-slot="default"/>
                </t>
            </div>
        `;
    
        setup() {
            this.state = useState({
                hasError: false,
                error: null
            });
        }
    
        catchError(error) {
            console.error("Error de componente capturado:", error);
    
            // Enviar al servicio de reporte de errores
            if (window.errorReporting) {
                window.errorReporting.captureException(error);
            }
    
            this.state.hasError = true;
            this.state.error = error;
        }
    
        retry() {
            this.state.hasError = false;
            this.state.error = null;
        }
    }
    

    2. Feature Flags para Despliegues Seguros

    export class Counter extends Component {
        setup() {
            this.featureFlags = useService("feature_flags");
            this.state = useState({
                count: this.props.initialValue || 0
            });
        }
    
        increment() {
            if (this.featureFlags.isEnabled("advanced_counter")) {
                // Nueva implementación
                this.state.count += this.props.step || 1;
            } else {
                // Respaldo seguro
                this.state.count++;
            }
        }
    }
    

    Ejercicios

    Ejercicio 1: Escribe un Ciclo de TDD

    Siguiendo el ciclo Rojo-Verde-Refactorizar, escribe una prueba que falle para un nuevo getter doubled en Counter que devuelva state.count * 2, implementa el código mínimo para que pase, y luego refactoriza si ves margen de mejora.

    Ejercicio 2: Hacer Mock de un Servicio y Verificar sus Argumentos

    Escribe una prueba para TaskManager.deleteTask que haga mock del servicio orm directamente con mockService("orm", ...) (en vez de pasar por onRpc) y verifique que orm.unlink fue llamado con ["project.task", [taskId]].

    Ejercicio 3: Esboza una Verificación de CI

    Esboza (en comentarios, sin necesidad de ejecutarlo) qué agregarías a test_js.py para que CI falle el build si alguna prueba de hoot de tu módulo falla. En 2-3 frases, explica por qué ejecutar pruebas JS en CI importa aunque ya pasen en tu máquina.


    Resumen: Construyendo Aplicaciones OWL Robustas

    Las pruebas y la depuración no son ideas tardías: son partes integrales de construir aplicaciones OWL profesionales. Aquí está tu hoja de ruta hacia la excelencia:

    1. Estrategia de Pruebas

    • Pruebas Unitarias: Probar componentes individuales de forma aislada
    • Pruebas de Integración: Probar interacciones entre componentes
    • Pruebas de Extremo a Extremo: Probar flujos de trabajo completos del usuario
    • Pruebas de Rendimiento: Asegurar tiempos de respuesta aceptables
    • Pruebas de Manejo de Errores: Verificar modos de falla elegantes

    2. Kit de Herramientas de Depuración

    • DevTools del Navegador: Tu interfaz principal de depuración
    • Logging Estratégico: Capturar cambios importantes de estado
    • Límites de Error: Manejo elegante de errores
    • Monitoreo de Rendimiento: Rastrear y optimizar operaciones lentas

    3. Prácticas Profesionales

    • Desarrollo Dirigido por Pruebas: Escribir pruebas antes de la implementación
    • Integración Continua: Ejecución automatizada de pruebas
    • Cobertura de Código: Asegurar cobertura adecuada de pruebas
    • Documentación: Especificaciones claras y ejecutables

    4. Mentalidad de Mantenimiento

    • Refactorizar con Confianza: Las buenas pruebas permiten cambios seguros
    • Prevención de Regresiones: Las pruebas detectan rupturas inesperadas
    • Documentación Viviente: Las pruebas muestran cómo deberían funcionar los componentes
    • Colaboración en Equipo: Entendimiento compartido a través de pruebas

    Al dominar estas técnicas de pruebas y depuración, te transformas de alguien que escribe código que funciona hoy en alguien que construye aplicaciones robustas y mantenibles que continúan funcionando mientras evolucionan. Esta es la marca distintiva del desarrollo profesional de OWL.

    Recuerda: cada error que detectes en una prueba es un error que tus usuarios nunca verán. Cada técnica de depuración que domines es tiempo ahorrado en futuras investigaciones. Invierte en estas habilidades, y te pagarán dividendos a lo largo de tu carrera profesional.

    TL;DR: TDD, mocking de servicios, CI y depuración segura en producción son lo que separa "funciona" de "sigue funcionando" — invierte en ellos en cuanto un componente hace algo de lo que un cliente realmente depende.

    Pruébalo tú mismo: El ejemplo de este capítulo está disponible como addon instalable de Odoo 19: simplifyit_owl_book_ch16_2_ex1. Las instrucciones de instalación están en el README del repositorio.

    ¿Qué Sigue?

    Pruebas y depuración asumen que sigues escribiendo OWL 2.0 puro. El Capítulo 17 Parte 1 cubre la realidad de la mayoría de los proyectos de Odoo: puentear componentes OWL nuevos con el sistema de widgets legado que todavía corre en producción.

    en Libro OWL
    Capítulo 17 Parte 1: El Puente Legacy-OWL - Comprensión e Implementación Básica
    Reading the whole book? Take it with you — free, no sign-up.
    PDF EPUB All chapters
    Enlaces útiles
    • Inicio
    • Sobre nosotros
    • Blog
    • El libro de OWL 2.0
    • Ayuda
    • Contáctanos
    Sobre nosotros

    Simplify It S.R.L. es una firma de implementación y desarrollo Odoo con base en La Paz, Bolivia, que trabaja con empresas de Latinoamérica y Norteamérica. Desarrollamos sobre Odoo desde la versión 6.1: módulos a medida, migraciones de versión y localización boliviana.

    También somos autores del libro de OWL 2.0, que publicamos gratis capítulo a capítulo en nuestro blog.

    Conecta con nosotros
    • Contáctanos
    • [email protected]
    • +591 65144144
    • La Paz, Bolivia
    Síguenos
    Copyright © Simplify It S.R.L.
    English (US) | Español (BO)
    Con la tecnología de Odoo - Cree un sitio web gratuito