Part 02 · Testing & Code Quality · 2.1.07

Transaction, locking và concurrency test

Concurrency test phải điều khiển interleaving và chứng minh invariant, không dựa vào xác suất scheduler tạo collision.


1. Transaction boundary và visibility

Mỗi transaction cần connection/session độc lập. Muốn test commit visibility, ghi và commit ở transaction A rồi đọc từ transaction B; đừng đọc lại bằng persistence context đã cache entity.

H2 không chứng minh PostgreSQL dialect, MVCC, locking, collation hay constraint semantics. Với behavior phụ thuộc database, dùng đúng engine và version family như production.

Production signal: test-managed rollback hữu ích cho isolation nhưng có thể che after-commit listener, commit visibility và async work. Những behavior đó cần một transaction thật và reader độc lập.

2. Optimistic và pessimistic locking

Optimistic locking: A và B đọc cùng version; A commit trước, B phải thất bại khi update version cũ. Assert lỗi conflict và bảo toàn invariant.

Pessimistic locking: A giữ lock, B cạnh tranh và phải block hoặc timeout theo cấu hình. Mọi future/wait trong test đều phải có timeout để CI không treo.

3. Deterministic scheduling

var ready = new CountDownLatch(workers);
var start = new CountDownLatch(1);

// Worker: ready.countDown(); start.await(); executeConflict();
assertTrue(ready.await(2, TimeUnit.SECONDS));
start.countDown();

Latch hoặc barrier chỉ điều khiển checkpoint đã biết. Với race tinh vi, dùng hook/fake repository hoặc stress test có seed và số iteration được kiểm soát. sleep chỉ đoán timing và tạo flaky test.

4. Assert business invariant

Sau concurrency, không chỉ đếm exception. Kiểm tra tổng tiền được bảo toàn, balance không âm, một idempotency key chỉ tạo một effect, version tăng đúng và transaction thất bại không để partial state.

5. Failure modes trong production

6. Checklist tự đánh giá

Nguồn tham khảo