Hai mươi chín bài trước đầy những hành vi mà không mock nào tái hiện được: 406 khi khai lệch tham số, 541 với tổ hợp cờ bị cấm, x-death tăng dần, prefetch bị bỏ qua khi autoAck=true, vòng lặp requeue 11 189 lần. Nếu test của bạn dùng một RabbitTemplate giả, mọi thứ đó đều xanh.

Bài này về cách chạy broker thật trong test mà không biến bộ test thành nửa tiếng chờ đợi.

Cấu trúc

@SpringBootTest
@Testcontainers
class DonHangTest {

    @Container
    @ServiceConnection                       // Spring Boot tự nối vào container này
    static final RabbitMQContainer RMQ = new RabbitMQContainer("rabbitmq:4");

    @Autowired RabbitTemplate tpl;
    @Autowired RabbitAdmin admin;

    @Test void thong_diep_khong_khop_binding_thi_mat() { ... }
}

Hai chi tiết quyết định thời gian chạy nằm ở dòng khai container: từ khoá static, và @Container đặt ở mức lớp. Cả hai làm cùng một việc — container sống suốt cả lớp test thay vì dựng lại cho từng phương thức.

Container dùng chung hay riêng

Đo bằng cách khởi động chính những container đó, ba "test" mỗi lần:

Thời gian
Một container dùng chung cả lớp 3,6 s
Mỗi test một container riêng 9,1 s

Chậm gấp 2,5 lần với chỉ ba test. Con số đó tuyến tính theo số test: một lớp 20 phương thức là khoảng 58 giây so với 5 giây — và đó là một lớp.

Bỏ static khỏi khai báo container là lỗi tốn thời gian nhất trong toàn bộ bài này, và nó không báo gì cả: test vẫn xanh, chỉ chậm. Sau vài tháng thì "bộ test RabbitMQ chạy lâu lắm" trở thành sự thật hiển nhiên mà không ai nhớ vì sao.

Muốn nhanh hơn nữa thì bật tái dùng container giữa các lần chạy (.withReuse(true) cộng testcontainers.reuse.enable=true trong ~/.testcontainers.properties) — hữu ích trên máy lập trình viên, nhưng nhớ dọn trạng thái ở đầu mỗi lớp vì hàng đợi cũ vẫn còn đó.

Ảnh gọn hơn không nhanh hơn

Lời khuyên hay gặp là dùng ảnh không có giao diện quản trị cho test. Đo thử:

Ảnh Tới khi nhận được kết nối
rabbitmq:4-management 2,8 s
rabbitmq:4 2,9 s

Không khác gì. Thời gian khởi động là thời gian node Erlang lên, không phải thời gian nạp plugin quản trị. Nên cứ dùng bản -management: khi một test hỏng khó hiểu, mở localhost:<port> xem hàng đợi đang có gì là cách gỡ nhanh nhất, và phần 5 cho thấy giao diện đó nói được nhiều thứ.

Đừng chờ bằng Thread.sleep

Cách viết phổ biến nhất trong test nhắn tin, và cũng tệ nhất:

tpl.convertAndSend("don-moi", don);
Thread.sleep(2000);              // "chắc là tới rồi"
assertThat(daNhan).isNotNull();

Năm phép chờ như vậy so với năm phép chờ bằng thư viện polling:

Cách chờ Thời gian 5 lần
Thread.sleep(2000) 10,11 s
Awaitility, poll mỗi 10 ms 0,12 s

Nhanh hơn 84 lần, và quan trọng hơn: nó không bị hỏng ngẫu nhiên. Thread.sleep(2000) vừa quá lâu cho trường hợp bình thường (thông điệp tới sau ~20 ms) vừa quá ngắn cho lần CI chạy chậm — đó là công thức của test đỏ ngẫu nhiên.

tpl.convertAndSend("don-moi", don);
await().atMost(Duration.ofSeconds(5))
       .untilAsserted(() -> assertThat(daNhan.get()).isNotNull());

Nên kiểm thử cái gì

Broker thật đắt hơn mock, nên đừng dùng nó để kiểm tra logic nghiệp vụ — thứ đó test đơn vị làm nhanh hơn. Dùng nó cho đúng những chỗ mà sê-ri này đo được là hay sai:

Topology có khớp không — hàng đợi, binding, routing key. Phần 8 đo được rằng sai một ký tự trong khoá là thông điệp biến mất không báo.

Đường lỗi có chạy không — ném ngoại lệ trong listener rồi kiểm tra thông điệp có sang nghĩa địa không. Phần 28 cho thấy hành vi này phụ thuộc cả vào loại ngoại lệ.

Chuyển đổi thông điệpphần 26 đo được rằng __TypeId__ trói hai dịch vụ vào nhau; một test gửi–nhận thật bắt được chuyện đó, mock thì không.

Bài sau: trace id đi theo thông điệp, và vì sao ngữ cảnh truy vết dừng lại ngay tại basicPublish.

Thử ba mươi giây

# xem test cua ban co dung chung container khong
docker events --filter 'event=start' --filter 'type=container' &
mvn test -Dtest=DonHangTest

Đếm số dòng start hiện ra. Nhiều hơn một cho một lớp test nghĩa là bạn quên static.