Tại sao code ứng dụng pass 100% test mà hệ thống vẫn crash?
Bạn đã bao giờ tự tin vì code Node.js hay Java phủ kín Unit Test, nhưng vừa deploy lên production là hệ thống “sập” ngay lập tức? Thủ phạm thường là các Trigger bị lỗi logic hoặc Function trả về sai kiểu dữ liệu sau khi nâng cấp. Đôi khi, chỉ một thay đổi nhỏ ở kiểu dữ liệu cột cũng đủ làm toàn bộ ứng dụng ngừng hoạt động.
Thực tế, nhiều developer coi Database là một “hộp đen” chỉ dùng để lưu trữ. Tuy nhiên, khi logic nghiệp vụ được đẩy xuống tầng DB để tối ưu hiệu năng, việc thiếu Unit Test chẳng khác nào đặt một quả bom hẹn giờ. pgTAP ra đời để giúp bạn tháo ngòi nổ đó.
pgTAP là gì?
pgTAP là một extension dành riêng cho PostgreSQL. Nó cung cấp các hàm kiểm thử theo giao thức TAP (Test Anything Protocol). Bạn có thể viết script kiểm tra cấu trúc bảng, ràng buộc (Constraints) hay các hàm xử lý phức tạp bằng chính ngôn ngữ SQL quen thuộc.
Điều tuyệt nhất? pgTAP chạy ngay trong Database. Bạn chỉ cần bọc các test case vào một Transaction rồi ROLLBACK là xong. Mọi dữ liệu rác sẽ biến mất, trả lại môi trường sạch sẽ như ban đầu.
Cài đặt pgTAP trong 2 phút
Nếu dùng Ubuntu hoặc Debian, bạn chỉ cần một dòng lệnh để cài đặt:
sudo apt-get install postgresql-15-pgtap # Đổi '15' theo version bạn đang dùng
Sau đó, hãy kích hoạt extension trong database:
CREATE EXTENSION pgtap;
Để xem báo cáo trực quan trên Terminal, hãy cài thêm pg_prove. Đây là công cụ giúp bạn chạy test hàng loạt cực kỳ chuyên nghiệp:
sudo cpan TAP::Parser::SourceHandler::pgTAP
Thực hành kiểm thử thực tế
Dưới đây là 3 kịch bản kiểm thử mà mình thường xuyên áp dụng để bảo vệ dữ liệu.
1. Kiểm tra cấu trúc (Schema)
Đừng để lỗi “cột không tồn tại” làm phiền bạn. Hãy đảm bảo bảng users luôn có cột email với định dạng chuẩn.
BEGIN;
SELECT plan(3);
-- Kiểm tra bảng và cột
SELECT has_table('users', 'Bảng users phải tồn tại');
SELECT col_type_is('users', 'email', 'text', 'Cột email phải là kiểu text');
SELECT col_is_unique('users', 'email', 'Cột email bắt buộc phải là Unique');
SELECT * FROM finish();
ROLLBACK;
2. Kiểm tra Logic Function
Giả sử bạn có hàm calculate_discount. Thay vì test tay, hãy để pgTAP xác nhận kết quả chính xác đến từng con số thập phân.
BEGIN;
SELECT plan(2);
-- Test case: Giảm 10% cho đơn hàng 100k
SELECT results_eq(
'SELECT calculate_discount(100000)',
'SELECT 90000::numeric',
'Hàm phải trả về 90,000 khi đầu vào là 100,000'
);
SELECT * FROM finish();
ROLLBACK;
Trong các dự án lớn, mình thường phải xử lý hàng nghìn dòng dữ liệu mẫu từ CSV. Để tiết kiệm thời gian, mình hay dùng tool tại toolcraft.app/vi/tools/data/csv-to-json để convert nhanh sang JSONB. Công cụ này chạy hoàn toàn trên trình duyệt, rất an toàn cho dữ liệu nội bộ.
3. Kiểm tra Trigger “ngầm”
Trigger rất khó debug nếu chỉ nhìn bằng mắt. Hãy viết test để chắc chắn cột updated_at luôn tự cập nhật khi có thay đổi.
BEGIN;
SELECT plan(1);
INSERT INTO users (id, username) VALUES (1, 'dev_test');
SELECT pg_sleep(0.1); -- Nghỉ một chút để lệch timestamp
UPDATE users SET username = 'dev_updated' WHERE id = 1;
SELECT ok(
updated_at > created_at,
'Trigger phải tự cập nhật thời gian khi update user'
) FROM users WHERE id = 1;
SELECT * FROM finish();
ROLLBACK;
Tự động hóa với CI/CD
Thay vì chạy thủ công, hãy gom tất cả file vào thư mục /tests. Chỉ với một lệnh, bạn sẽ biết toàn bộ hệ thống có ổn hay không:
pg_prove -U postgres -d my_database tests/*.sql
Nếu có bất kỳ test case nào fail, pg_prove sẽ trả về exit code khác 0. Điều này giúp Pipeline trên GitHub Actions hoặc GitLab CI dừng lại ngay lập tức, ngăn chặn rủi ro làm hỏng database Staging.
Kinh nghiệm thực tế cho bạn
Sau nhiều năm làm việc với PostgreSQL, đây là 3 quy tắc mình luôn tuân thủ:
- Luôn dùng Transaction: Cặp lệnh
BEGINvàROLLBACKlà vật bất ly thân. Nó giúp database của bạn luôn sạch sẽ sau mỗi lần test. - Tách biệt môi trường: Tuyệt đối không chạy test trên DB đang dev chung. Hãy tạo một instance riêng để tránh xung đột dữ liệu với đồng nghiệp.
- Ưu tiên logic phức tạp: Đừng tốn thời gian test những thứ hiển nhiên. Hãy tập trung vào các Store Procedure dài hàng trăm dòng hoặc các Trigger chồng chéo lên nhau.
Lời kết
Viết Unit Test cho database có thể khiến bạn mất thêm chút thời gian ban đầu. Tuy nhiên, nó giúp bạn tiết kiệm 30-40% thời gian debug về sau. Khi có pgTAP bảo vệ, bạn sẽ tự tin hơn mỗi khi cần refactor những hệ thống lớn. Thử cài đặt ngay hôm nay để thấy sự khác biệt!

