Pragmatic Programmer – Topic 42: Property-Based Testing — Để Framework Tự Tìm Bug Cho Bạn
Mở đầu
Có bao giờ bạn viết unit test, pass hết, rồi lên production vẫn gặp bug không? Chuyện đó quen lắm. Cái problem là: mấy cái test bạn viết chỉ kiểm tra những gì bạn nghĩ là sẽ xảy ra. Còn edge case mà bạn không ngờ tới thì sao?
Pragmatic Programmer Topic 42 — Property-Based Testing — là câu trả lời cho vấn đề đó. Thay vì bạn tự nghĩ ra input rồi assert cái output, bạn nói cho framework biết tính chất mà code của bạn phải luôn đúng, và để nó tự generate ra hàng trăm, hàng ngàn input ngẫu nhiên để kiểm tra.
Ảnh: ThisIsEngineering — Pexels
Property-based testing là gì?
Nói đơn giản, nó là một cách testing khác với cái kiểu "example-based testing" truyền thống mà dev nào cũng quen:
- Example-based:
assert add(1, 2) == 3— bạn nghĩ ra 1 input, kiểm tra output. - Property-based:
assert property: add(a, b) == add(b, a)— bạn nói "cộng có tính giao hoán", framework tự generate ra 1000 cặp (a, b) ngẫu nhiên để kiểm tra.
Ý tưởng này được QuickCheck (Haskell) giới thiệu từ hồi 1999, và giờ thì hầu như ngôn ngữ nào cũng có thư viện tương tự: fast-check cho JS/TypeScript, hypothesis cho Python, proptest cho Rust, fscheck cho C#.
Ảnh: cottonbro studio — Pexels
Tại sao nó mạnh?
Cái hay của property-based testing là nó đi tìm edge case mà bạn không nghĩ tới. Hồi mình mới tập tành với fast-check trong TypeScript, mình viết thử một cái validate email:
const emailRegex = /^[\w.-]+@[\w.-]+\.\w+$/;
Viết example test vài cái như "test@example.com", "user@domain.co" — pass hết. Nhưng khi dùng property-based test:
test('email validation should accept valid emails', () => {
fc.assert(
fc.property(fc.email(), (email) => {
expect(validateEmail(email)).toBe(true);
})
);
});
Bùm — fail ngay với cái email có dấu + ở trước @ (first.last+tag@domain.com). Kiểu mà mình không hề nghĩ tới khi viết example test.
"Property-based testing finds bugs that example-based testing never would." — Pragmatic Programmer
Shrink — cái làm nên sự khác biệt
Một điểm cực kỳ thông minh của property-based testing là shrinking. Khi framework tìm được input làm fail property, nó không báo "lỗi với input X" rồi thôi — nó thu nhỏ input đó xuống mức tối thiểu còn gây lỗi.
Ví dụ, nếu một property fail với chuỗi dài 500 ký tự, shrink sẽ rút gọn xuống còn 3-4 ký tự — giúp bạn biết chính xác cái gì đang sai, không bị no vote với input khổng lồ.
Khi nào nên xài?
Từ kinh nghiệm mò mẫm của mình, property-based testing rất hiệu quả với:
- Parsing / serialization — encode rồi decode có ra lại như cũ không?
- Thuật toán xử lý dữ liệu — sort, filter, map có đúng tính chất không?
- Business logic — từ input bất kỳ, output có luôn thoả mãn constraint nhất định không?
- API validation — input gì cũng không được crash server
Còn mấy cái đơn giản như CRUD, UI rendering thì example-based vẫn ổn. Không phải cái gì cũng cần property-based.
Kết
Property-based testing là một kỹ thuật mà mình ước gì đã biết sớm hơn. Nó không thay thế được unit test thông thường, nhưng bổ sung một layer phát hiện lỗi mà cách kia không làm được.
Nếu bạn chưa thử, hãy bắt đầu với một function có tính chất rõ ràng (ví dụ: reverse(reverse(x)) === x) — chỉ 5 phút là thấy được cái hay liền.
📋 Phụ lục thuật ngữ
- Property-based testing — kỹ thuật kiểm thử dựa trên tính chất, dùng input ngẫu nhiên do framework sinh ra
- Shrinking — cơ chế thu nhỏ input fail xuống mức tối thiểu, giúp debug dễ hơn
- Example-based testing — kiểm thử truyền thống, viết input-output cụ thể cho từng test case
- Invariant — tính chất luôn đúng của code, bất kể input là gì