Ir al contenido

TDD en módulos Odoo: desarrollo guiado por pruebas en Odoo 19

Red → Green → Refactor con TransactionCase, ejemplos reales y buenas prácticas
16 de junio de 2026 por
TDD en módulos Odoo: desarrollo guiado por pruebas en Odoo 19
Aitor Atencia

Odoo · Testing · TDD

El Test-Driven Development (desarrollo guiado por pruebas) cambia el orden: primero el test que falla, luego el código mínimo que lo pasa, y después el refactor. En Odoo funciona — y te ahorra regresiones en cada -u.

Logo Odoo TDD by Example
Red → Green → Refactor: el ciclo que define el TDD.

Introducción

¿Por qué TDD en módulos Odoo?

Odoo es un framework enorme con herencias en cadena, triggers ORM, computed fields y cron jobs. Un cambio inocente en write() puede romper tres módulos aguas abajo. Los tests automatizados son tu red de seguridad; el TDD te obliga a escribirlos antes, lo que mejora el diseño del API interno de tu módulo.

«Si no está testeado, no funciona — solo no lo sabes todavía.»

Ciclo

Red → Green → Refactor

1

🔴 Red

Escribe un test que describe el comportamiento deseado. Falla porque el código no existe.

2

🟢 Green

Implementa lo mínimo para que el test pase. Sin optimizar, sin extras.

3

🔵 Refactor

Limpia el código con confianza. Los tests te avisan si rompes algo.

Ciclo TDD
El ciclo se repite para cada comportamiento nuevo.

Ejemplo

TDD paso a paso: descuento por volumen

Imagina un módulo que aplica un 10% de descuento en pedidos de venta con más de 10 unidades totales.

🔴 Paso 1 — Test que falla

# mi_modulo/tests/test_sale_discount.py
from odoo.tests import TransactionCase

class TestSaleVolumeDiscount(TransactionCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.product = cls.env['product.product'].create({{
            'name': 'Test Product',
            'list_price': 100.0,
        }})
        cls.partner = cls.env['res.partner'].create({{
            'name': 'Test Customer',
        }})

    def test_volume_discount_applied_above_threshold(self):
        """Orders with qty > 10 get 10% discount."""
        order = self.env['sale.order'].create({{
            'partner_id': self.partner.id,
            'order_line': [(0, 0, {{
                'product_id': self.product.id,
                'product_uom_qty': 15,
            }})],
        }})
        order.action_apply_volume_discount()
        line = order.order_line[0]
        self.assertEqual(line.discount, 10.0)

Ejecutas el test → falla porque action_apply_volume_discount no existe.

🟢 Paso 2 — Código mínimo

# mi_modulo/models/sale_order.py
from odoo import models

VOLUME_THRESHOLD = 10
VOLUME_DISCOUNT = 10.0

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    def action_apply_volume_discount(self):
        for order in self:
            total_qty = sum(order.order_line.mapped('product_uom_qty'))
            if total_qty > VOLUME_THRESHOLD:
                order.order_line.write({{'discount': VOLUME_DISCOUNT}})

Ejecutas el test → pasa. 🎉

🔴 Paso 3 — Segundo test (borde)

    def test_no_discount_below_threshold(self):
        """Orders with qty <= 10 keep 0% discount."""
        order = self.env['sale.order'].create({{
            'partner_id': self.partner.id,
            'order_line': [(0, 0, {{
                'product_id': self.product.id,
                'product_uom_qty': 5,
            }})],
        }})
        order.action_apply_volume_discount()
        self.assertEqual(order.order_line[0].discount, 0.0)

Probablemente ya pasa con el código actual. Si no, ajustas hasta que pase.

🔵 Paso 4 — Refactor

    def _get_total_quantity(self):
        self.ensure_one()
        return sum(self.order_line.mapped('product_uom_qty'))

    def action_apply_volume_discount(self):
        for order in self:
            if order._get_total_quantity() > VOLUME_THRESHOLD:
                order.order_line.write({{'discount': VOLUME_DISCOUNT}})

Ejecutas todos los tests → siguen pasando. Código más legible, mismo comportamiento.

Framework

Herramientas de testing en Odoo 19

Clase baseCuándo usarlaVelocidad
TransactionCaseTests de modelos, ORM, workflowsMedia (rollback al final)
SavepointCaseTests aislados con savepointsRápida
HttpCaseControladores, rutas web, portalLenta (HTTP real)
Tagged('post_install')Tests que necesitan módulos instalados
# Ejecutar tests de un módulo
docker compose exec odoo \
  /opt/odoo/venv/bin/python /opt/odoo/odoo/odoo-bin \
  -c /opt/odoo/odoo.conf \
  -d odoo \
  --test-enable \
  --stop-after-init \
  -i mi_modulo \
  --log-level=test

Buenas prácticas

Reglas para tests Odoo efectivos

  • Nombres descriptivos: test_confirm_sale_order_updates_state_to_sale
  • Un assert por concepto: no mezcles validación de precio y de estado
  • setUpClass para datos compartidos: no recrees productos en cada test
  • No dependas de IDs fijos: usa self.env.ref() o crea registros
  • Mockea integraciones externas: APIs, SMTP, pasarelas de pago
  • Tests atómicos: cada test debe poder ejecutarse solo
  • Cobertura mínima 80% en código nuevo; lógica crítica ~100%
Python
Los tests Odoo son Python puro con acceso completo al ORM.

Qué testear

Prioridades en módulos Odoo

CapaQué testearEjemplo
Modeloscreate, write, computes, constraintstest_age_computed_from_birth_date
WorkflowsTransiciones de estado, botones de accióntest_confirm_creates_diagnostic_test
SeguridadACL, record rules, acceso por grupotest_user_cannot_read_other_partner_tests
WizardsFlujos multi-paso, valores por defectotest_wizard_assigns_professional
CronJobs programados, idempotenciatest_birthday_cron_sends_once_per_year
ControladoresRutas HTTP, permisos, respuestasHttpCase con self.url_open()

Integración

TDD en el flujo de trabajo diario

  1. Recibes la tarea / historia de usuario
  2. Escribes el test que describe el criterio de aceptación
  3. Verificas que falla (Red)
  4. Implementas el mínimo (Green)
  5. Refactorizas si hace falta (Refactor)
  6. Commit con mensaje convencional: test: o feat:
  7. CI ejecuta tests antes del merge

Resumen

TDD en Odoo no requiere herramientas exóticas: TransactionCase, el ORM y disciplina. Escribe el test primero, implementa lo mínimo, refactoriza con confianza. Combinado con SOLID, obtienes módulos que se pueden evolucionar sin miedo — y eso, en un ERP que vive años, no tiene precio.

en Odoo
SOLID en módulos Odoo: principios de diseño para desarrolladores
Single Responsibility, herencia, mixins y arquitectura mantenible en Odoo 19