Biết dựng TransactionCase và điều khiển nó bằng @tagged rồi, giờ tới phần dễ làm ẩu nhất: bên trong test viết gì. Một test xanh chưa chắc là test tốt — tệ nhất là test trông như kiểm tra gì đó nhưng thật ra không khẳng định điều gì, nên nó xanh cả khi code đã hỏng. Bài này nói cách dựng dữ liệu gọn, chọn đúng phép assert, và tránh những cái bẫy "xanh giả".
Dựng dữ liệu: create, setUpClass, ref, new
Có mấy cách đưa dữ liệu vào test, chọn theo nhu cầu:
self.env['model'].create({...})— cách chính, tạo bản ghi thật trong giao dịch test (sẽ cuộn ngược sau).setUpClassthay vìsetUp— dựng dữ liệu một lần cho cả lớp thay vì mỗi test, nhanh hơn khi dữ liệu nền dùng chung.self.env.ref('module.xmlid')— lấy bản ghi đã có sẵn từ file XML (nhóm quyền, dữ liệu mẫu)..new({...})— tạo bản ghi ảo trong bộ nhớ, không ghi CSDL — hữu ích khi chỉ cần thửonchange/compute mà không cần lưu.

Hình 1: setUpClass dựng hai thẻ mẫu một lần. Ba kiểu assert: assertRecordValues so nhiều field của nhiều bản ghi cùng lúc; assertRaises khẳng định thao tác ném đúng ngoại lệ; subTest lặp nhiều case trong một test — nếu case (50→70) hỏng, các case còn lại vẫn chạy, và log chỉ ra đúng case nào sai.
assertRecordValues: người bạn thân của Odoo
Đây là phép assert đặc trưng của Odoo mà unittest thuần không có. Thay vì viết mười dòng assertEqual cho mười field, assertRecordValues(recordset, [{...}, {...}]) so một lượt nhiều field của nhiều bản ghi — thứ tự dict khớp thứ tự bản ghi. Khi lệch, nó in ra đúng field nào của đúng bản ghi nào sai, gọn hơn hẳn.
Bẫy "xanh giả" — và bằng chứng assert báo lỗi rõ
Đây là phần quan trọng nhất. Vài kiểu test xanh mà vô dụng:
assertTrue(recordset_rỗng): một recordset rỗng là falsy, nênself.env['model'](không search) làmassertTruethất bại — hoặc tệ hơn, bạn tưởng nó kiểm "model tồn tại" mà thật ra đang kiểm "có bản ghi". (Đã vấp đúng bẫy này ở bài trước.)- Quên
flush: ràng buộc CSDL chỉ kích hoạt khi dữ liệu được đẩy xuống DB; kiểm ngay sau khi gán mà chưa flush thì ràng buộc chưa nổ, test "xanh" nhầm. - Test không có
assertnào: chạy code rồi kết thúc — luôn xanh, chẳng kiểm gì. - Dùng
==thayassertEqual:self.bac.diem == 70là một biểu thức bị vứt đi, không khẳng định gì. PhảiassertEqual— và đây là lý do:

Hình 2: Thật. Ở phần 1, mình cố ý viết assertEqual(self.bac.diem, 999). Test hỏng với thông báo AssertionError: 70 != 999 — nói ngay giá trị thật (70) so với kỳ vọng (999). Đây là thứ == không bao giờ cho bạn: assertEqual in giá trị thực tế khi sai, giúp gỡ lỗi trong vài giây. Phần 2: sửa kỳ vọng về đúng 70 → cả 4 test xanh.
Chọn phép assert cho đúng
self.assertEqual(a, b) # a phải bằng b (in cả hai khi sai)
self.assertTrue(x) / assertFalse(x) # x đúng/sai — CẨN THẬN recordset rỗng
self.assertIn(x, tap) # x nằm trong tập/chuỗi
with self.assertRaises(ValidationError): # khối lệnh phải NÉM lỗi
...
self.assertRecordValues(rs, [{...}]) # so nhiều field nhiều record (Odoo)
Mỗi phép in một thông báo lỗi khác nhau khi hỏng; chọn đúng phép làm log dễ đọc. assertEqual cho giá trị hai vế, assertRaises cho biết lỗi mong đợi có nổ không, assertRecordValues chỉ ra field lệch.
Ba ý mang về
- Dựng dữ liệu gọn:
createcho bản ghi thật,setUpClassdựng một lần cho cả lớp,env.reflấy dữ liệu XML sẵn,.newcho bản ghi ảo không lưu DB. - Chọn đúng phép assert:
assertRecordValuesso nhiều field nhiều record một lượt (đặc trưng Odoo),assertRaisescho đường thất bại,subTestđể một case hỏng không chặn case khác. - Tránh "xanh giả": đừng
assertTrue(recordset rỗng), đừng quên flush trước khi kiểm ràng buộc DB, đừng viết test không có assert, và luôn dùngassertEqualchứ không==— đã thấyassertEqualin70 != 999rõ ràng, còn==thì im lặng.
Test đã vững, phần còn lại của sê-ri chuyển sang những mảng "vận hành" của một module chuyên nghiệp. Phần sau bàn về đa ngôn ngữ (i18n) — cách Odoo tách chuỗi dịch ra file .pot, dịch sang .po, để module của bạn nói được tiếng Việt lẫn tiếng Anh mà không sửa code.