Infrastructure test bằng Testcontainers
Container tạm thời tăng fidelity với production, nhưng chỉ đáng tin khi lifecycle, readiness, networking và data isolation được thiết kế rõ.
1. Architecture và lifecycle
Test process dùng Docker-compatible runtime để tạo container, map port ngẫu nhiên, theo dõi resource và dọn khi test kết thúc. Generic container cung cấp primitive; module PostgreSQL, Kafka hoặc Redis thêm connection API và default phù hợp với technology đó.
Image tag phải được pin gần với production. Dùng tag trôi nổi như latest làm test thay đổi theo thời gian và khiến failure khó tái hiện.
@Testcontainers
class OrderRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:17-alpine");
}
2. Readiness không phải startup
Process started chưa chắc service đã nhận request. Wait strategy phải quan sát signal phù hợp: log message, listening port, health endpoint hoặc query có ý nghĩa. Chờ một khoảng sleep cố định thường tạo flaky test và làm CI chậm.
- Chọn readiness signal phản ánh đúng dependency có thể sử dụng.
- Startup timeout đủ rộng cho CI lạnh nhưng không biến lỗi thành pipeline treo.
- Khi timeout, in log và container state để chẩn đoán thay vì chỉ báo
connection refused.
3. Scope container và data isolation
Per-test container cô lập tốt nhưng chậm. Static per-class hoặc singleton suite nhanh hơn nhưng cần reset schema/data, quản lý sequence, connection state, broker offset và external side effect.
Transaction rollback không reset mọi thứ: sequence, background worker, broker offset và side effect bên ngoài vẫn có thể tồn tại. Reuse container có thể hữu ích ở local, nhưng test CI không được phụ thuộc vào container đã chạy từ trước.
4. Networking và configuration
Test process thường kết nối tới container qua mapped host port; container-to-container dùng Docker network alias và internal port. Không hard-code localhost hoặc port cố định.
- Đăng ký dynamic properties hoặc Spring Boot service connection trước khi application context startup.
- Trong network nhiều container, dùng network alias thay vì địa chỉ host của máy chạy test.
- Kiểm tra timeout, TLS và authentication giống production khi đó là risk cần chứng minh.
5. CI operations
- Docker daemon/socket và image pull permission phải sẵn sàng trên runner.
- Pin image version, cache hợp lý và giới hạn số container chạy song song.
- Lưu logs, inspect output và container state khi test fail.
- Dọn resource kể cả khi test process bị exception hoặc bị kill.
- Không đưa secret production vào container test; dùng credential tạm thời và scope tối thiểu.
- Đo thời gian startup/pull để phân biệt test chậm do application hay do infrastructure.
6. Failure modes
- Readiness giả: container “running” nhưng service chưa accept request.
- Tag trôi nổi: cùng test nhưng image behavior đổi theo registry.
- Port hard-code: pass trên máy cá nhân nhưng conflict trên CI.
- State leak: static container không reset schema, sequence, offset hoặc cache.
- Container-to-host confusion: dùng
localhostbên trong container để gọi service khác. - Cleanup failure: orphan container/volume làm runner cạn tài nguyên.
7. Checklist tự đánh giá
- Tôi chọn được module/container phù hợp với infrastructure thật.
- Tôi phân biệt startup signal và readiness signal.
- Tôi biết scope container nào phù hợp với isolation và runtime budget.
- Tôi cấu hình dynamic port/network mà không hard-code localhost.
- Tôi có log, cleanup và credential policy cho CI.
- Tôi không dùng local container reuse như dependency bắt buộc của CI.