Sê-ri đã dựng đủ thứ: model, controller, webhook. Giờ tới câu hỏi khiến code sống lâu hay chết yểu: làm sao sửa mà không sợ vỡ thứ khác? Câu trả lời là test tự động. Odoo có sẵn khung test rất gọn, và viên gạch đầu tiên là lớp TransactionCase. Điều làm nó đặc biệt: mỗi test chạy trong một giao dịch riêng rồi tự cuộn ngược — bạn tạo bao nhiêu dữ liệu trong test cũng được, xong là sạch bong, không để lại một vết trên CSDL thật. Bài này viết 4 test cho module quan_ca_phe, chạy thật, rồi soi CSDL để chứng minh điều đó.
Cấu trúc một TransactionCase
Test nằm trong thư mục tests/ của module (Odoo tự phát hiện — không cần khai vào __init__.py hay manifest). Một lớp test kế thừa TransactionCase, dựng dữ liệu trong setUp, rồi mỗi method test_* là một phép kiểm:

Hình 1: File test. setUp() chạy trước mỗi method test, dựng một thẻ TEST-0001. Bốn test kiểm bốn hành vi thật của model: nút gia hạn cộng 20 điểm, ràng buộc chặn điểm âm, tên hiển thị gắn nhãn hạng, và sự cô lập dữ liệu. self.env là entry point tới ORM y như trong model; self.assertEqual/assertIn/assertRaises là các phép khẳng định của unittest.
Chạy thật: --test-enable
Test không tự chạy khi bạn dùng Odoo bình thường. Kích hoạt bằng --test-enable kèm --test-tags để chọn đúng module:

Hình 2: Thật. Bốn test khởi động, rồi dòng chốt: 0 failed, 0 error(s) of 4 tests — tất cả xanh, chạy trong 0.05 giây. Lưu ý --test-tags=/quan_ca_phe: dấu / trước tên module nghĩa là "chỉ test của module này". Với server đang chạy sẵn, mình cho tiến trình test dùng cổng khác (--http-port) để không đụng cổng 8069.
Phép màu cuộn ngược: chứng minh bằng CSDL
Nghe "tự cuộn ngược" thì dễ, nhưng đây là bằng chứng. Test test_moi_test_co_du_lieu_sach khẳng định TEST-0001 là duy nhất — nếu các test không cô lập, thẻ do setUp của test này tạo sẽ chồng lên thẻ của test trước và số đếm vọt lên. Nó vẫn bằng 1. Và sau khi cả 4 test chạy xong, mình soi CSDL thật bằng odoo shell:
>>> env['quan.the.thanh.vien'].search_count([('name','=','TEST-0001')])
0 # KHÔNG còn thẻ test nào — đã cuộn ngược sạch
>>> sum(env['quan.the.thanh.vien'].search([]).mapped('diem'))
385 # tổng điểm y như trước khi test (4 thẻ gốc, không đổi)
Bốn test đã tạo thẻ, cộng điểm, thử vi phạm ràng buộc — vậy mà CSDL không nhích một ly. Đó là vì TransactionCase mở một savepoint đầu mỗi test và ROLLBACK về đó khi xong. Bạn viết test thoải mái mà không cần dọn dẹp thủ công.
TransactionCase so với các loại khác
Odoo có vài lớp test, chọn đúng loại là quan trọng:
| Lớp | Dùng khi | Đặc điểm |
|---|---|---|
TransactionCase |
Kiểm logic model/ORM | Mỗi test rollback, nhanh, không HTTP |
HttpCase |
Kiểm route/controller, chạy tour JS | Có server HTTP thật, chậm hơn |
Form (trợ giúp) |
Mô phỏng người dùng điền form | Kích hoạt onchange như giao diện |
TransactionCase là loại bạn dùng nhiều nhất — phần lớn logic nghiệp vụ nằm ở model, và nó nhanh vì không dựng server web.
Vài lưu ý khi viết
setUpvssetUpClass:setUpchạy trước mỗi test (dữ liệu tươi mỗi lần);setUpClasschạy một lần cho cả lớp (nhanh hơn khi dữ liệu nền dùng chung, nhưng phải cẩn thận không để test này ảnh hưởng test kia).- Đừng
self.env.cr.commit()trong test. Commit thật phá cơ chế rollback, để lại rác trong CSDL — đúng thứTransactionCasesinh ra để tránh. - Đặt tên test rõ nghĩa. Tên method là thứ hiện trong log khi test hỏng;
test_diem_khong_duoc_amnói ngay điều gì vỡ, hơn hẳntest_1. - Test cả đường thất bại.
assertRaises(ValidationError)kiểm rằng ràng buộc thật sự chặn — một ràng buộc âm thầm không hoạt động còn nguy hơn không có.
Ba ý mang về
TransactionCaselà lớp nền viết test model trong Odoo:setUpdựng dữ liệu, mỗitest_*dùngself.env+self.assert*để kiểm một hành vi. Thư mụctests/được tự phát hiện.- Mỗi test chạy trong một giao dịch riêng rồi tự cuộn ngược — đã chứng minh: sau 4 test tạo/sửa dữ liệu, CSDL không còn thẻ TEST-0001 nào và tổng điểm vẫn 385. Không cần dọn thủ công.
- Chạy bằng
--test-enable --test-tags=/module; chọnTransactionCasecho logic ORM (nhanh, rollback),HttpCasekhi cần server/tour thật.
Test cơ bản chạy được rồi, nhưng ta vừa lướt qua hai cái nhãn @tagged('post_install', '-at_install') mà chưa nói kỹ. Phần sau mổ xẻ tagged, post_install và at_install — chúng quyết định test của bạn chạy ở thời điểm nào trong quá trình cài, và vì sao chọn sai nhãn khiến test lúc xanh lúc đỏ.