Skip to content

AI Coding Agent for Writing Odoo Tests

DeployMonkey Team · March 23, 2026 13 min read

Why Testing Matters (and Why It's Skipped)

Testing is the most important and most neglected part of Odoo development. Developers write models, views, and controllers under deadline pressure, promising to add tests later. Later never comes. Then a module upgrade breaks something, a security rule allows unauthorized access, or a computed field returns wrong values — and nobody catches it until a customer reports the issue. AI coding agents can generate comprehensive test suites from existing code, covering the cases developers meant to test but never did.

What AI Test Agents Generate

1. CRUD Tests

# AI reads your model and generates basic CRUD tests:

class TestRentalEquipment(TransactionCase):
    """Test rental.equipment model operations."""

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.category = cls.env['rental.equipment.category'].create({
            'name': 'Power Tools',
        })
        cls.equipment = cls.env['rental.equipment'].create({
            'name': 'Drill Press DP-500',
            'serial_number': 'DP-500-001',
            'category_id': cls.category.id,
            'daily_rate': 45.00,
            'weekly_rate': 250.00,
            'monthly_rate': 800.00,
            'state': 'available',
        })

    def test_create_equipment(self):
        """Test equipment creation with all required fields."""
        self.assertEqual(self.equipment.name, 'Drill Press DP-500')
        self.assertEqual(self.equipment.state, 'available')
        self.assertEqual(self.equipment.daily_rate, 45.00)

    def test_serial_number_unique(self):
        """Test that duplicate serial numbers are rejected."""
        with self.assertRaises(Exception):
            self.env['rental.equipment'].create({
                'name': 'Another Drill',
                'serial_number': 'DP-500-001',  # duplicate
            })

    def test_required_fields(self):
        """Test that creation fails without required fields."""
        with self.assertRaises(Exception):
            self.env['rental.equipment'].create({
                'serial_number': 'DP-500-002',
                # missing 'name' — required
            })

2. Workflow Tests

# AI generates tests for state transitions:

class TestRentalAgreement(TransactionCase):
    """Test rental agreement workflow."""

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.customer = cls.env['res.partner'].create({
            'name': 'Test Customer',
            'email': '[email protected]',
        })
        cls.equipment = cls.env['rental.equipment'].create({
            'name': 'Excavator EX-200',
            'serial_number': 'EX-200-001',
            'daily_rate': 350.00,
            'state': 'available',
        })

    def test_agreement_draft_to_active(self):
        """Test confirming a rental agreement."""
        agreement = self.env['rental.agreement'].create({
            'partner_id': self.customer.id,
            'equipment_id': self.equipment.id,
            'start_date': fields.Date.today(),
        })
        self.assertEqual(agreement.state, 'draft')
        agreement.action_confirm()
        self.assertEqual(agreement.state, 'active')
        self.assertEqual(self.equipment.state, 'rented')

    def test_cannot_rent_unavailable_equipment(self):
        """Test that rented equipment can't be rented again."""
        self.equipment.state = 'rented'
        with self.assertRaises(ValidationError):
            self.env['rental.agreement'].create({
                'partner_id': self.customer.id,
                'equipment_id': self.equipment.id,
                'start_date': fields.Date.today(),
            })

    def test_return_releases_equipment(self):
        """Test that returning equipment sets it available."""
        agreement = self._create_active_agreement()
        agreement.action_return()
        self.assertEqual(agreement.state, 'returned')
        self.assertEqual(self.equipment.state, 'available')

3. Access Rights Tests

# AI generates security tests — often the most neglected:

class TestRentalSecurity(TransactionCase):
    """Test access rights and record rules."""

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.rental_user = cls.env['res.users'].create({
            'name': 'Rental User',
            'login': 'rental_user',
            'groups_id': [(6, 0, [
                cls.env.ref('equipment_rental.group_rental_user').id
            ])],
        })
        cls.basic_user = cls.env['res.users'].create({
            'name': 'Basic User',
            'login': 'basic_user',
            'groups_id': [(6, 0, [
                cls.env.ref('base.group_user').id
            ])],
        })

    def test_rental_user_can_read(self):
        """Rental users can read equipment records."""
        equipment = self.env['rental.equipment'].with_user(
            self.rental_user
        ).search([])
        self.assertTrue(len(equipment) >= 0)  # no access error

    def test_basic_user_cannot_access(self):
        """Users without rental group cannot access equipment."""
        with self.assertRaises(AccessError):
            self.env['rental.equipment'].with_user(
                self.basic_user
            ).search([])

    def test_rental_user_cannot_delete(self):
        """Regular rental users cannot delete equipment."""
        equipment = self.env['rental.equipment'].create({
            'name': 'Test', 'serial_number': 'TEST-001',
        })
        with self.assertRaises(AccessError):
            equipment.with_user(self.rental_user).unlink()

4. Computed Field Tests

# AI tests that computed fields return correct values:

def test_rental_charge_daily(self):
    """Test daily rate charge calculation."""
    agreement = self._create_active_agreement()
    agreement.start_date = fields.Date.today() - timedelta(days=3)
    agreement.action_return()
    # 3 days × $350/day = $1,050
    self.assertEqual(agreement.total_charge, 1050.00)

def test_rental_charge_weekly_rate(self):
    """Test that weekly rate applies for 7+ day rentals."""
    agreement = self._create_active_agreement()
    agreement.start_date = fields.Date.today() - timedelta(days=10)
    agreement.action_return()
    # 1 week ($2,000) + 3 days ($1,050) = $3,050
    self.assertEqual(agreement.total_charge, 3050.00)

def test_rental_charge_monthly_rate(self):
    """Test that monthly rate applies for 30+ day rentals."""
    agreement = self._create_active_agreement()
    agreement.start_date = fields.Date.today() - timedelta(days=35)
    agreement.action_return()
    # 1 month ($8,000) + 5 days ($1,750) = $9,750
    self.assertEqual(agreement.total_charge, 9750.00)

Edge Cases AI Catches

  • Null/empty values: what happens when optional fields are empty?
  • Boundary conditions: exactly 7 days (daily or weekly rate?)
  • Concurrent access: two users renting the same equipment simultaneously
  • Date edge cases: leap years, timezone boundaries, midnight crossings
  • Large values: 999,999 quantity, very long text fields
  • Special characters: product names with quotes, ampersands, unicode

Test Coverage Report

CategoryWithout AIWith AI
CRUD operationsSometimes testedAlways tested
Workflow transitionsHappy path onlyHappy + error paths
Access rightsRarely testedAll groups tested
Computed fieldsSometimes testedAll computations
ConstraintsRarely testedAll SQL + Python
Edge casesAlmost neverCommon edges covered
Overall coverage20-40%70-85%

AI Test Generation Workflow

  1. Point AI agent at your module directory
  2. AI reads models, views, and security files
  3. AI generates test file with comprehensive test cases
  4. Developer reviews, adjusts expected values, adds domain-specific tests
  5. Run tests: odoo --test-enable -u your_module --stop-after-init
  6. Fix any failures, iterate with AI for additional coverage

DeployMonkey AI Test Writer

DeployMonkey's AI coding agent generates test suites for your Odoo modules. Point it at your code and get comprehensive tests for models, workflows, security, computed fields, and edge cases. Run tests automatically on every deployment with DeployMonkey's CI pipeline.