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:

Ảnh chụp mã Python nền tối file test_the.py. Import TransactionCase từ odoo tests common, tagged từ odoo tests, ValidationError từ odoo exceptions. Decorator tagged post_install và trừ at_install. Class TestTheThanhVien kế thừa TransactionCase. Method setUp gọi super rồi gán self The là env quan the thanh vien và tạo self the với name TEST-0001 hang_the vang diem 100. Bốn method test: test_gia_han_cong_20_diem gọi action_gia_han rồi assertEqual diem 120 và trang_thai dang_dung; test_diem_khong_duoc_am dùng with assertRaises ValidationError khi gán diem âm 5; test_display_name_kem_hang assertIn Vàng và TEST-0001 trong display_name; test_moi_test_co_du_lieu_sach dùng search_count kiểm TEST-0001 là duy nhất

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:

Ảnh chụp terminal nền tối chạy lệnh odoo test thật. Dòng lệnh odoo -d blog19 --test-enable --test-tags gạch chéo quan_ca_phe -u quan_ca_phe --stop-after-init. Log INFO blog19 loading module quan_ca_phe 94 phần 175. Bốn dòng Starting TestTheThanhVien test_diem_khong_duoc_am, test_display_name_kem_hang, test_gia_han_cong_20_diem, test_moi_test_co_du_lieu_sach. Dòng stats quan_ca_phe 6 tests 0.05 giây 144 queries. Dòng result 0 failed 0 errors of 4 tests when loading database blog19 tô màu xanh

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

  • setUp vs setUpClass: setUp chạy trước mỗi test (dữ liệu tươi mỗi lần); setUpClass chạ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ứ TransactionCase sinh 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_am nói ngay điều gì vỡ, hơn hẳn test_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ề

  1. TransactionCase là lớp nền viết test model trong Odoo: setUp dựng dữ liệu, mỗi test_* dùng self.env + self.assert* để kiểm một hành vi. Thư mục tests/ được tự phát hiện.
  2. 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.
  3. Chạy bằng --test-enable --test-tags=/module; chọn TransactionCase cho logic ORM (nhanh, rollback), HttpCase khi 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 đỏ.