Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Write and run Odoo automated tests using TransactionCase, HttpCase, and browser tour tests. Covers test data setup, mocking, and CI integration.
.claude/skills/odoo-automated-tests/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-10 | ✗→✓ | ▲ Improved | — | — |
| case-15 | ✗→✗ | = Same ✗ | — | — |
| case-01 | ✗→✗ | = Same ✗ | — | — |
| case-03 | ✗→✗ | = Same ✗ | — | — |
Odoo has a built-in testing framework based on Python's unittest. This skill helps you write TransactionCase unit tests, HttpCase integration tests, and JavaScript tour tests. It also covers running tests in CI pipelines.
--test-enable.@odoo-automated-tests and describe the feature to test.odoo CLI command to execute your tests.python# tests/test_hospital_patient.py from odoo.tests.common import TransactionCase from odoo.tests import tagged from odoo.exceptions import ValidationError @tagged('post_install', '-at_install') class TestHospitalPatient(TransactionCase): @classmethod def setUpClass(cls): # Use setUpClass for performance — runs once per class, not per test super().setUpClass() cls.Patient = cls.env['hospital.patient'] cls.doctor = cls.env['res.users'].browse(cls.env.uid) def test_create_patient(self): patient = self.Patient.create({ 'name': 'John Doe', 'doctor_id': self.doctor.id, }) self.assertEqual(patient.state, 'draft') self.assertEqual(patient.name, 'John Doe') def test_confirm_patient(self): patient = self.Patient.create({'name': 'Jane Smith'}) patient.action_confirm() self.assertEqual(patient.state, 'confirmed') def test_empty_name_raises_error(self): with self.assertRaises(ValidationError): self.Patient.create({'name': ''}) def test_access_denied_for_other_user(self): # Test security rules by running as a different user other_user = self.env.ref('base.user_demo') with self.assertRaises(Exception): self.Patient.with_user(other_user).create({'name': 'Test'})
> setUpClass vs setUp: Use setUpClass (Odoo 15+) for shared test data. It runs once per class and is significantly faster than setUp which re-initializes for every single test method.
bash# Run all tests for a specific module ./odoo-bin --test-enable --stop-after-init -d my_database -u hospital_management # Run only tests tagged with a specific tag ./odoo-bin --test-enable --stop-after-init -d my_database \ --test-tags hospital_management # Run a specific test class ./odoo-bin --test-enable --stop-after-init -d my_database \ --test-tags /hospital_management:TestHospitalPatient
pythonfrom odoo.tests.common import HttpCase from odoo.tests import tagged @tagged('post_install', '-at_install') class TestPatientController(HttpCase): def test_patient_page_authenticated(self): # Authenticate as a user, not with hardcoded password self.authenticate(self.env.user.login, self.env.user.login) resp = self.url_open('/hospital/patients') self.assertEqual(resp.status_code, 200) def test_patient_page_redirects_unauthenticated(self): # No authenticate() call = public/anonymous user resp = self.url_open('/hospital/patients', allow_redirects=False) self.assertIn(resp.status_code, [301, 302, 403])
setUpClass() with cls.env instead of setUp() — it is dramatically faster for large test suites.@tagged('post_install', '-at_install') to run tests after all modules are installed.ValidationError, AccessError, UserError).self.with_user(user) to test access control without calling sudo().TransactionCase test is rolled back in isolation.HttpCase.authenticate() — use self.env.user.login or a fixture user.phantomjs or Chrome headless) and a live Odoo server — not covered in depth here.HttpCase tests are significantly slower than TransactionCase — use them only for controller/route verification.cr.commit()) can leak state between tests.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of -20 percentage points is the difference between those two pass rates over the 22 comparable cases. 3 cases got worse with the skill loaded, and they are included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.