用途
Odoo 内置了基于 Python unittest 的测试框架。该技能帮助开发者编写 TransactionCase 单元测试、HttpCase 集成测试和 JavaScript tour 测试,并覆盖在 CI 流水线中运行测试的相关内容。
适用场景包括:
- 为自定义模型的业务逻辑编写单元测试。
- 创建 HTTP 测试以验证控制器端点。
- 调试 CI 流水线中的测试失败。
- 通过 --test-enable 配置自动化测试执行。
主要能力
该技能可生成完整的测试类代码,包含 setup、teardown 和断言,并提供执行测试所需的 odoo CLI 命令。
使用方式为:提及 @odoo-automated-tests 并描述要测试的功能,即可生成测试代码与运行命令。
使用方式与示例
TransactionCase 单元测试(Odoo 15+ 模式)
# 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 与 setUp 的对比:使用 setUpClass(Odoo 15+)共享测试数据。它每个类只运行一次,比每个测试方法都重新初始化的 setUp 快得多。
通过 CLI 运行测试
# 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
使用 HttpCase 测试控制器
from 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() 配合 cls.env,而不是 setUp()——对大型测试套件而言速度显著更快。
- 建议:使用 @tagged('post_install', '-at_install') 在所有模块安装后运行测试。
- 建议:同时测试正常路径和错误条件(ValidationError、AccessError、UserError)。
- 建议:使用 self.with_user(user) 测试访问控制,而不调用 sudo()。
- 避免:使用生产数据库进行测试——始终使用专用测试数据库。
- 避免:依赖测试执行顺序——每个 TransactionCase 测试都在隔离状态下回滚。
- 避免:在 HttpCase.authenticate() 中硬编码密码——使用 self.env.user.login 或固定用户。
限制
- JavaScript tour 测试需要运行中的浏览器(通过 phantomjs 或 Chrome headless)和实时 Odoo 服务器——此处未深入覆盖。
- HttpCase 测试比 TransactionCase 慢得多——仅用于控制器/路由验证。
- 不覆盖模拟外部服务(例如在测试中模拟 SMTP 服务器或支付网关)。
- 测试隔离是在事务级别而非数据库级别——提交数据的测试(例如通过 cr.commit())可能在测试之间泄漏状态。