A monolithic architecture for our clients' hundreds of versions: how we write and support tests

Bài tóm tắt Bumble 2019 3. Quy mô lớn Độ khó: Trung bình

Bumble · 2019 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc

Bối cảnh

  • Backend Badoo là monolith PHP 12 năm tuổi, khởi đầu không có test. Backend developer viết tính năng và đưa lên production trước mọi client (app mobile, site), bọc bằng feature flag hoặc A/B test; client phát hành sau tuỳ ưu tiên, có khi nửa năm sau. Người viết backend chịu trách nhiệm tới khi client cuối cùng tích hợp xong, nên muốn chắc tính năng còn chạy lúc đó.

Thay đổi chính

  • Unit test cho code cũ: thư viện SoftMocks (mã nguồn mở) chặn include/require và viết lại file lúc chạy, nên mock được hằng số, hàm, method static/private/final. Quy tắc kèm theo, kiểm ở review: code mới phải test được bằng mock chuẩn của PHPUnit; SoftMocks chỉ cho code cũ hoặc khi cách khác quá đắt, để công cụ không thành cớ viết code khó test.
  • Mutation testing: coverage không nói test có kiểm gì không, nên làm đột biến code (true→false, >→>=) rồi chạy test phủ dòng đó; test vẫn qua là test yếu. Tự viết công cụ vì Humbug, Infection không chạy cùng SoftMocks.
  • Integration test với DB: DBMocks chặn wrapper truy vấn, đọc tên DB + bảng, tạo bảng tạm cùng cấu trúc (SHOW CREATE TABLE) trên tmpfs ở cùng host, chuyển truy vấn sang đó, tên bảng có khoá riêng mỗi test. Hết bận tâm cấu trúc DataSet, không ghi bẩn bảng thật, vẫn kiểm tương thích với đúng phiên bản MySQL, và chạy song song không khoá nhau.
  • API test giả lập phiên client theo đúng giao thức: pool test user dùng lại và khôi phục về trạng thái lúc đăng ký sau mỗi test (đăng ký mới mỗi lần thì chậm, khó xoá, và vấp các tiến trình nền như nhân bản sang DC khác). Chạy cả trên cụm pre-production dùng dữ liệu và service production: cờ is_test_user gán lúc đăng ký, không đổi, mọi service cô lập theo cờ và loại khỏi analytics và kết quả A/B. QA API là backdoor có tài liệu để đặt trạng thái user (nghề, ép vào nhánh A/B, đổi ngày đăng ký): chỉ truy cập từ mạng văn phòng, cần khoá bí mật, chỉ chạy với test user. RemoteMocks bật mock phía backend cho riêng session của test.
  • Quy tắc dùng API test (chậm, đi qua mạng, session, DB): kiểm giao thức client–server và tích hợp, chuỗi hành động phức tạp; không dùng cho biến thể nhỏ của response. UI test do đội QA viết sau khi tính năng đã ổn định, vì không đáng tự động hoá tính năng có thể không qua nổi A/B test.
  • Chạy nhanh (2019): 100.000 unit test, 6.000 integration test, 14.000 API test; chạy một luồng mất 40 phút, 90 phút và 10 giờ. Dựng cloud chạy test song song; chia chunk theo thời gian chạy đo được (mỗi chunk khoảng 30 giây, xếp chặt để không quá nhiều chunk); tách cloud làm hai phần để luôn còn chỗ cho test nhanh. Kết quả: unit trong 1 phút, integration 5 phút, API 15 phút; cả bộ từ khoảng 12 giờ xuống dưới 22 phút.
  • Chọn test theo coverage: từ diff của nhánh ra danh sách file, ra test phủ các file đó; coverage thu mỗi ngày lúc hạ tầng dev rảnh. Dùng được cho patch gấp. Hạn chế: backend phát hành hai lần mỗi ngày nên coverage lệch một build, vì vậy build phát hành vẫn chạy hết; API test phủ quá rộng nên đổi một file dễ kéo cả bộ API test.

Vì sao

  • Theo Badoo: monolith lớn thì đổi một chỗ có thể vỡ chỗ khác, nên phải chạy nhiều test; muốn chạy nhiều mà nhanh thì cần thống kê thời gian và coverage của từng test.

Bài học cho SWE

  • Công cụ giúp test code cũ phải đi kèm quy tắc giới hạn chỗ dùng, nếu không nó hợp thức hoá code khó test.
  • Test trên dữ liệu và hạ tầng thật cần một danh tính test cố định từ lúc tạo, được mọi service và cả analytics tôn trọng.
  • Backdoor cho test chấp nhận được nếu có ba lớp chặn: mạng, khoá, và chỉ tác động lên đối tượng test.

Liên quan