Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là QA lead của một công ty fintech ở Sài Gòn. Một năm trước, bạn tự hào viết được bộ 500 test tự động, chạy xanh mướt mắt, sếp khen, cả team hưng phấn. Nhưng hôm nay, mở Jenkins ra bạn thấy: 250 test đỏ. Bạn ngồi debug từng cái, và phát hiện điều đau lòng — chỉ có 12 test đỏ vì thực sự có bug. 238 test còn lại đỏ vì… nút bấm đổi màu, đổi tên id, API thêm một field, môi trường staging bị reset dữ liệu. Không có bug nào cả. Test của bạn đang "báo cháy giả".
Đây chính là thực tế phũ phàng mà mọi kỹ sư automation đều đụng phải: viết test chỉ là một nửa công việc, nửa còn lại là bảo trì (maintenance) chúng. Người mới thường nghĩ automation là "viết một lần, chạy mãi mãi". Sai lầm. Automation là một tài sản phần mềm sống, và mọi tài sản sống đều cần được chăm sóc, cắt tỉa, sửa chữa liên tục.
Bài học này không dạy bạn viết test mới (những bài khác lo việc đó). Bài này dạy bạn chiến lược giữ cho bộ test đã có luôn khỏe mạnh, đáng tin và không trở thành gánh nặng khiến cả team muốn xóa bỏ automation. Nếu bạn muốn theo nghề QA automation lâu dài ở Việt Nam, đây là kỹ năng phân biệt giữa một người "biết viết Selenium" và một SDET thực thụ.
Khái niệm cốt lõi
Test maintenance là gì?
Test maintenance là toàn bộ công việc cần làm để giữ cho bộ test tự động tiếp tục phản ánh đúng hành vi mong đợi của sản phẩm khi sản phẩm thay đổi. Nó bao gồm: cập nhật test khi UI/API thay đổi, sửa test flaky (test lúc pass lúc fail), xóa test lỗi thời, refactor test trùng lặp, và cập nhật dữ liệu test.
Một con số đáng nhớ từ thực tế: theo kinh nghiệm chung của ngành, khi bộ test lớn dần, chi phí bảo trì có thể chiếm 40–60% tổng thời gian của đội automation. Nếu bạn không có chiến lược, con số này phình to đến mức test tự động trở nên tốn kém hơn cả test tay — và đó là lúc management ra quyết định khai tử toàn bộ.
Phân biệt "test fail vì bug" và "test fail vì maintenance"
Đây là điểm mấu chốt cần khắc cốt ghi tâm. Khi một test đỏ, có hai khả năng:
- True failure (thất bại thật): Test đỏ vì sản phẩm thực sự có lỗi. Đây là lúc test làm đúng nhiệm vụ — nó cứu bạn khỏi một bug lọt production.
- False failure (thất bại giả): Test đỏ nhưng sản phẩm vẫn đúng. Nguyên nhân đến từ chính test hoặc môi trường, không phải code nghiệp vụ.
Các nguyên nhân khiến test "gãy" (causes of breakage)
Hãy hiểu rõ kẻ thù. Test tự động thường gãy vì những nguyên nhân sau:
1. Thay đổi UI (UI change). Đây là nguyên nhân số một với test giao diện. Dev đổi <button id="btn-login"> thành <button class="cta-primary">, thế là mọi locator trỏ tới btn-login chết ngay. Đổi text nút từ "Đăng nhập" sang "Bắt đầu", đổi luồng từ 2 bước thành 3 bước — tất cả đều làm gãy test.
2. Thay đổi locator không ổn định. Test dùng XPath dài kiểu /html/body/div[3]/div[2]/span[1] sẽ gãy ngay khi dev thêm một <div> bọc ngoài. Locator "giòn" (brittle) là nguồn cơn của phần lớn maintenance.
3. Thay đổi API/contract. API thêm field bắt buộc, đổi status code từ 200 sang 201, đổi cấu trúc JSON response — test API đứt hết.
4. Vấn đề dữ liệu (test data). Test giả định user test01@shop.vn có sẵn 5 đơn hàng. Ai đó chạy test khác xóa mất dữ liệu, hoặc DB staging bị reset hằng tuần — test đỏ dù code không đổi.
5. Timing và bất đồng bộ. Đây là mảnh đất màu mỡ cho flaky test — trang tải chậm hơn thường lệ, animation chưa xong, API phản hồi trễ. (Chiến lược wait chi tiết thuộc về bài khác, ở đây ta chỉ nhìn nó dưới góc độ nguyên nhân gãy.)
6. Phụ thuộc môi trường. Test chạy được trên máy bạn nhưng đỏ trên CI vì khác múi giờ, khác ngôn ngữ locale, khác phiên bản trình duyệt.
Chiến lược cốt lõi: giảm bề mặt bảo trì
Nguyên tắc vàng: cách bảo trì tốt nhất là thiết kế để ít phải bảo trì. Ba trụ cột chiến lược:
- Tập trung hóa (centralization): Mỗi locator, mỗi endpoint, mỗi dữ liệu test chỉ khai báo ở MỘT nơi. Khi thay đổi, bạn sửa một chỗ thay vì 200 chỗ. (Đây là lý do Page Object Model tồn tại — chi tiết ở bài riêng.)
- Ổn định hóa (stability): Ưu tiên locator bền, dùng data test cô lập, xử lý bất đồng bộ đúng cách để test không "gãy vô cớ".
- Cắt tỉa (pruning): Chủ động xóa test lỗi thời, gộp test trùng. Bộ test nhỏ mà tinh luôn dễ bảo trì hơn bộ test khổng lồ mà loạn.
Tình huống thực tế
Tình huống 1 — Sàn TMĐT "ShopViet" và cơn ác mộng 500 test
ShopViet (giả định) là một sàn thương mại điện tử tại TP.HCM, đội QA 6 người. Trong 18 tháng, họ tích lũy 520 UI test bằng Selenium. Vấn đề bắt đầu khi team frontend chuyển từ giao diện cũ sang React với thư viện component mới. Kết quả: 60% test đỏ chỉ trong một sprint.
Khi mổ xẻ, họ phát hiện nguyên nhân gốc: mỗi test viết locator trực tiếp trong file test, và cùng một nút "Thêm vào giỏ" được viết locator theo 14 cách khác nhau rải rác khắp nơi. Không có nguồn chân lý duy nhất. Để sửa, một kỹ sư phải mất 3 tuần dò từng file.
Bài học rút ra: ShopViet sau đó áp dụng quy tắc "một locator, một nơi định nghĩa" và thiết lập chỉ số theo dõi tên là maintenance ratio — số giờ sửa test chia cho số giờ viết test mới mỗi sprint. Ban đầu tỷ lệ này là 2.5 (sửa nhiều gấp 2.5 lần viết mới!). Sau 4 tháng refactor, họ kéo xuống 0.6. Con số này trở thành thước đo sức khỏe được báo cáo cho management mỗi sprint.
Tình huống 2 — Fintech "PayMinh" và test flaky làm sập niềm tin
PayMinh (giả định) là startup ví điện tử. Họ có 180 test chạy trên mỗi lần merge code. Vấn đề: khoảng 20 test "chập chờn" — lúc pass lúc fail ngẫu nhiên. Dev quen dần với việc thấy đỏ thì bấm "Re-run" cho tới khi xanh.
Rồi một ngày, một bug thật lọt qua: chức năng nạp tiền qua sandbox bị lỗi làm mất giao dịch. Test có bắt được lỗi này — nó đỏ. Nhưng dev, theo thói quen, bấm re-run ba lần, thấy vẫn đỏ nhưng nghĩ "chắc lại flaky", rồi… merge luôn. Bug ra production, ảnh hưởng khoảng 400 giao dịch trong 6 giờ.
Bài học rút ra: Flaky test không chỉ tốn thời gian, nó giết chết niềm tin vào toàn bộ hệ thống. PayMinh sau đó áp dụng chính sách cứng: bất kỳ test nào flaky quá 3 lần trong tuần sẽ bị cách ly (quarantine) ngay — tách ra khỏi luồng chặn merge, gắn nhãn, và giao cho một người chịu trách nhiệm sửa trong vòng 5 ngày. Nếu không sửa được, xóa hẳn. Nguyên tắc của họ: "Thà không có test còn hơn có test dối trá." (Việc quản lý flaky chuyên sâu có bài riêng; ở đây nó là một phần của chiến lược maintenance tổng thể.)
Tình huống 3 — Công ty gia công "TechBridge" và test lỗi thời chất đống
TechBridge (giả định) là công ty outsourcing ở Đà Nẵng, làm dự án cho khách châu Âu. Sau 2 năm, dự án tích lũy 900 test. Nhưng khi họ đo, chỉ 640 test còn chạy — 260 test bị @Disabled (tắt) rải rác qua nhiều đời nhân sự. Không ai dám xóa vì sợ "biết đâu còn cần". Chúng nằm đó, làm rối codebase, khiến người mới hoang mang không biết cái nào còn giá trị.
Bài học rút ra: Test bị disable mà không có ngày hết hạn là "rác kỹ thuật". TechBridge đặt ra quy tắc: mỗi test bị tắt phải kèm một ticket và deadline. Nếu quá 30 ngày không ai bật lại được, test đó bị xóa vĩnh viễn (git luôn giữ lịch sử nếu cần khôi phục). Họ dọn được 220 test chết, và thời gian onboarding người mới giảm rõ rệt vì bộ test giờ "sạch".
Hướng dẫn từng bước
Đây là quy trình xây dựng chiến lược maintenance mà bạn có thể áp dụng cho team của mình.
Bước 1 — Đo lường trước khi hành động. Bạn không thể cải thiện thứ không đo được. Thu thập 3 chỉ số cơ bản: (a) Pass rate — tỷ lệ xanh mỗi lần chạy; (b) False failure rate — trong các lần đỏ, bao nhiêu phần trăm KHÔNG phải bug thật; (c) Maintenance ratio — giờ sửa test / giờ viết test mới. Ghi lại hằng tuần.
Bước 2 — Phân loại nguyên nhân mỗi lần fail. Mỗi khi có test đỏ, gắn nhãn nguyên nhân: bug, ui-change, data, flaky, env. Chỉ cần một spreadsheet đơn giản hoặc label trong công cụ report. Sau 2–3 sprint, bạn sẽ thấy rõ "kẻ thù lớn nhất" của mình là gì để tập trung xử lý.
Bước 3 — Tập trung hóa mọi điểm dễ thay đổi. Rà soát và đưa tất cả locator, URL/endpoint, dữ liệu test vào một nơi tập trung (page objects, config file, constants). Mục tiêu: khi sản phẩm đổi X, bạn chỉ sửa đúng một dòng.
Bước 4 — Thiết lập chính sách flaky. Định nghĩa rõ "thế nào là flaky", ngưỡng cách ly, người chịu trách nhiệm, và deadline sửa. Quan trọng nhất: flaky test KHÔNG được phép chặn merge trong lúc chờ sửa, nhưng cũng KHÔNG được phép nằm im mãi.
Bước 5 — Lịch dọn dẹp định kỳ (test hygiene). Dành cố định một khoảng thời gian mỗi sprint — ví dụ nửa ngày thứ Sáu — cho "test gardening": xóa test chết, gộp test trùng, cập nhật test lỗi thời. Biến maintenance thành thói quen đều đặn thay vì "để dồn rồi khủng hoảng".
Bước 6 — Gắn trách nhiệm rõ ràng. Mỗi nhóm test nên có "chủ sở hữu". Khi test đỏ, hệ thống báo đúng người phụ trách chứ không phải "cha chung không ai khóc". Ownership là yếu tố quyết định giữa bộ test được chăm và bộ test bị bỏ rơi.
Bước 7 — Review test như review code sản phẩm. Mọi test mới phải qua code review với tiêu chí bảo trì: locator có bền không? Có phụ thuộc dữ liệu bên ngoài không? Có tự dọn dữ liệu sau khi chạy không? Chặn nợ kỹ thuật ngay từ cửa vào.
Lỗi thường gặp & mẹo
Lỗi 1: "Sửa cho xanh" thay vì sửa gốc. Nhiều bạn thấy test đỏ vì timing thì thêm đại một dòng chờ cứng cho qua, thấy locator gãy thì vá tạm bằng XPath còn giòn hơn. Đây là "vay nợ kỹ thuật lãi kép" — vài tháng sau nó quay lại gãy nặng hơn. Mẹo: mỗi lần sửa, hỏi "nguyên nhân gốc là gì?" và sửa tận gốc.
Lỗi 2: Không phân biệt được false failure với true failure. Team đối xử mọi lần đỏ như nhau, dẫn tới hoặc là hoảng loạn vô ích, hoặc là chai lì bỏ qua. Mẹo: bắt buộc gắn nhãn nguyên nhân cho mỗi lần fail (Bước 2 ở trên).
Lỗi 3: Sợ xóa test. Tâm lý "để đó biết đâu cần" khiến bộ test phình to đầy rác. Mẹo: nhớ rằng git giữ toàn bộ lịch sử — bạn luôn khôi phục được. Test chết nằm im còn nguy hiểm hơn test bị xóa, vì nó tạo ảo giác về độ phủ (coverage) mà thực chất không bảo vệ gì.
Lỗi 4: Ôm đồm test UI cho mọi thứ. UI test đắt đỏ nhất về maintenance. Nếu một logic có thể kiểm bằng test API hay unit test rẻ hơn nhiều, đừng nhét vào UI test. (Chiến lược phân tầng này thuộc bài Test Pyramid, nhưng nó ảnh hưởng trực tiếp tới gánh nặng maintenance của bạn.)
Lỗi 5: Không có ai sở hữu test. Test "của cả team" thực chất là test "của không ai". Mẹo: gán ownership rõ ràng theo module/tính năng.
Mẹo vàng: Coi mỗi test như một nhân viên. Nó phải "làm việc" (bắt bug thật) đủ để xứng đáng với "lương" (chi phí bảo trì) nó ngốn. Test nào ngốn nhiều mà chẳng bao giờ bắt được bug thật — hãy mạnh dạn cho nghỉ việc.
Bài tập thực hành
- Audit bộ test hiện có. Lấy một dự án có sẵn (của công ty hoặc project mẫu bạn tự làm). Đếm: tổng số test, số test đang bị disable, số test bạn nghi là flaky. Tính tỷ lệ test disable trên tổng. Nếu trên 10%, bạn có vấn đề về hygiene.
- Lập bảng phân loại nguyên nhân. Chạy bộ test 5 lần liên tiếp trên môi trường CI. Với mỗi test đỏ, ghi lại nguyên nhân (bug / ui-change / data / flaky / env). Vẽ biểu đồ xem nguyên nhân nào chiếm đa số. Đây là "kẻ thù số một" của bạn.
- Tập trung hóa một locator. Tìm trong dự án một element được viết locator lặp lại ở nhiều test. Refactor để nó chỉ định nghĩa một nơi duy nhất. Đo xem nếu element đó thay đổi, giờ bạn cần sửa mấy chỗ (đáp án lý tưởng: một).
- Viết chính sách flaky cho team. Soạn một tài liệu ngắn nửa trang: định nghĩa flaky, ngưỡng cách ly, ai chịu trách nhiệm, deadline sửa, khi nào thì xóa. Trình bày cho một đồng nghiệp và nhận phản hồi.
- Tính maintenance ratio. Trong sprint gần nhất, ước lượng số giờ bạn dành sửa test cũ so với viết test mới. Nếu tỷ lệ trên 1.0, hãy đề xuất một hành động cụ thể để kéo nó xuống trong sprint tới.
Tóm tắt
- Viết test chỉ là một nửa; bảo trì là nửa còn lại. Sau một năm, phần lớn thời gian automation sẽ đổ vào maintenance nếu bạn không có chiến lược.
- Phân biệt false failure và true failure là kỹ năng sống còn. False failure cao bào mòn niềm tin, và mất niềm tin thì bug thật cũng bị bỏ qua (như bài học đắt giá của PayMinh).
- Hiểu rõ nguyên nhân gãy: UI change, locator giòn, thay đổi API, dữ liệu test, timing, môi trường. Biết kẻ thù mới đánh trúng.
- Chiến lược cốt lõi ba trụ cột: tập trung hóa (sửa một nơi), ổn định hóa (giảm gãy vô cớ), và cắt tỉa (dũng cảm xóa rác).
- Quy trình vận hành: đo lường → phân loại nguyên nhân → tập trung hóa → chính sách flaky → dọn dẹp định kỳ → gán ownership → review test.
- Tư duy nền tảng: mỗi test phải "xứng đồng lương" nó ngốn. Test không xứng đáng thì cho nghỉ việc, đừng nuôi báo.