Mở đầu — vì sao bài này quan trọng
Có một sự thật mà rất ít khóa học automation testing nói thẳng với bạn: viết test tự động chỉ là phần dễ, nuôi test mới là phần khó. Khi bạn mới vào nghề, bạn háo hức viết 200 test case, thấy con số xanh lè chạy qua terminal và cảm thấy như một anh hùng. Nhưng sáu tháng sau, chính bạn (hoặc người kế nhiệm bạn) sẽ ngồi giữa một mớ 200 test đó, một nửa đang đỏ, một nửa đang bị @Disabled, và không ai dám xóa vì sợ mất coverage. Đội của bạn bắt đầu nói câu chết người: "Ừ test đó fail hoài, kệ nó đi."
Khoảnh khắc đội bạn bắt đầu bỏ qua kết quả test là khoảnh khắc bộ test của bạn chết về mặt giá trị — dù nó vẫn còn chạy. Và nguyên nhân sâu xa gần như luôn là: không ai lên ngân sách (budget) cho việc bảo trì test, và không ai chủ động refactor test code.
Bài này không dạy bạn viết thêm test. Bài này dạy bạn cách phân bổ thời gian giữa xây framework — viết test mới — bảo trì test cũ, cách dự báo chi phí bảo trì theo từng năm vòng đời dự án, và cách refactor test suite để chi phí đó không phình ra vô hạn. Đây là kỹ năng phân biệt một SDET junior với một người thực sự sở hữu (own) chất lượng của cả hệ thống test về lâu dài. Nếu bạn muốn bộ test của mình sống được 3-5 năm chứ không phải bị vứt đi và viết lại từ đầu mỗi 18 tháng, hãy đọc kỹ.
Khái niệm cốt lõi
Ba loại công việc trong đời sống một test suite
Toàn bộ thời gian của một đội automation, dù bạn có ý thức hay không, luôn được chia vào ba xô (bucket):
- Build (Xây dựng framework/hạ tầng): viết base framework, wrapper cho driver, utilities, cấu hình CI, reporting, test data setup. Đây là "đường xá" — làm một lần, dùng nhiều lần.
- Write (Viết test mới): thêm test case cho feature mới. Đây là công việc "tăng coverage".
- Maintain (Bảo trì test cũ): sửa test đỏ do UI đổi, cập nhật locator, ổn định flaky test, xóa test lỗi thời, refactor cho test dễ đọc. Đây là "phần chìm của tảng băng".
Phân bổ thời gian thực tế theo vòng đời dự án
Đây là phần cốt lõi bạn cần nắm. Tỉ lệ ba xô công việc trên dịch chuyển có quy luật theo năm tuổi của dự án:
Năm 1 — Giai đoạn xây nền:
- Build: ~70%
- Write test: ~30%
- Maintain: gần như 0%
Năm 2 — Giai đoạn tăng trưởng:
- Build: ~30% (chỉ mở rộng, thêm module)
- Write test: ~50%
- Maintain: ~20%
Năm 3 trở đi — Giai đoạn trưởng thành:
- Build: ~15%
- Write test: ~35%
- Maintain: ~50%
Quy tắc ngón tay cái để đặt ngân sách bảo trì
Một cách đơn giản để lượng hóa: mỗi test case sau khi viết xong sẽ "ngốn" khoảng 10-20% thời gian viết ban đầu của nó mỗi năm để bảo trì. Nghĩa là nếu bạn viết 500 test, mỗi test tốn trung bình 1 giờ viết, thì mỗi năm bạn cần khoảng 500-1000 giờ chỉ để giữ chúng sống. Đây là con số bạn phải mang ra bàn với quản lý khi lập kế hoạch năm.
Hệ quả quan trọng: mỗi test bạn thêm vào là một khoản nợ định kỳ, không phải tài sản một lần. Điều này dẫn thẳng tới nguyên tắc vàng: đừng automate mọi thứ chỉ vì bạn có thể. Một test flaky, giá trị thấp, đắt bảo trì thì nên bị xóa thẳng tay — xóa test đôi khi là hành động bảo trì có ROI cao nhất.
Refactoring test code: nợ kỹ thuật cũng sống trong test
Test code là code. Nó cũng bị duplication, cũng bị "God class", cũng có hard-code, cũng có magic number. Refactoring test không phải là xa xỉ — nó là cách bạn kéo đường chi phí bảo trì từ dốc lên thành đường phẳng. Các mục tiêu refactor chính:
- Gom locator/selector về một chỗ (thường qua Page Object) để một thay đổi UI chỉ sửa một dòng thay vì 40 dòng.
- Trích xuất helper/utility cho các thao tác lặp (login, tạo data, dọn data).
- Xóa test trùng lặp và test đã lỗi thời.
- Đặt tên rõ ràng để người sau đọc test biết ngay nó kiểm gì mà không cần đọc code.
Tình huống thực tế
Ví dụ 1 — Tiki: khi 40% pipeline thời gian là "sửa test đỏ"
Một đội QA tại một sàn thương mại điện tử lớn ở Việt Nam (bối cảnh tương tự Tiki) xây bộ UI automation cho luồng checkout bằng Selenium từ năm 2022. Năm đầu họ tự hào có 600 test, chạy nightly, coverage đẹp. Sang năm thứ hai, front-end team migrate sang một design system mới, đổi hàng loạt class CSS. Kết quả: một buổi sáng, 340/600 test đỏ.
Vấn đề không phải là UI đổi — đó là điều bình thường. Vấn đề là locator bị rải rác trực tiếp trong từng test. Mỗi nút "Thanh toán" được viết lại driver.findElement(By.cssSelector(".btn-checkout")) ở 50 file khác nhau. Đội mất gần 3 tuần chỉ để sửa cho xanh lại. Đo lại thời gian sprint, họ phát hiện gần 40% năng lực đội đang chảy vào việc chữa test đỏ — nhưng vì không ai đặt tên cho khoản đó trong kế hoạch, quản lý cứ nghĩ đội "chậm" trong việc ra tính năng mới.
Bài học: Chi phí bảo trì tồn tại dù bạn có đặt tên cho nó hay không. Nếu không đưa nó vào budget minh bạch, nó sẽ ăn ngầm vào năng lực viết test mới và làm cả đội trông kém hiệu quả. Và bài học kỹ thuật: locator rải rác = bom hẹn giờ. Sau vụ này họ refactor sang Page Object, và lần đổi UI tiếp theo họ chỉ mất 2 ngày thay vì 3 tuần.
Ví dụ 2 — Startup fintech Đông Nam Á: xóa 30% test suite để cứu tốc độ
Một startup ví điện tử ở khu vực (bối cảnh giả định hợp lý kiểu MoMo/GrabPay giai đoạn đầu) có bộ 1.200 test E2E sau 2,5 năm. Pipeline chạy mất 55 phút, và tỉ lệ flaky khiến cứ 3 lần merge thì 1 lần phải re-run. Đội trưởng SDET quyết định làm một việc phản trực giác: kiểm toán test suite (test audit).
Họ gắn tag mỗi test với hai số liệu lấy từ lịch sử CI 6 tháng: số lần fail giả (false failure) và số lần bắt được bug thật. Kết quả gây choáng: có 380 test chưa từng bắt được một bug thật nào, trong khi chiếm phần lớn số lần flaky. Đội mạnh dạn xóa hẳn 30% test suite. Pipeline giảm còn 32 phút, độ tin cậy tăng vọt, và — quan trọng nhất — thời gian bảo trì hàng sprint giảm gần một nửa.
Bài học: "Nhiều test hơn" không đồng nghĩa với "chất lượng cao hơn". Một test không bắt bug nhưng hay flaky là một khoản nợ ròng. Refactoring bao gồm cả xóa, và việc xóa cần dựa trên dữ liệu (bug bắt được, tỉ lệ flaky) chứ không phải cảm tính.
Ví dụ 3 — Công ty outsourcing: chuẩn hóa "20% cho bảo trì" trong sprint
Một công ty phần mềm gia công ở TP.HCM nhận dự án automation cho khách Nhật. Ban đầu họ commit 100% năng lực cho việc viết test mới để đạt KPI coverage. Đến tháng thứ tám, test suite đỏ triền miên, khách hàng mất niềm tin. Team lead đàm phán lại và đưa ra một chính sách đơn giản: mỗi sprint dành cố định 20% (khoảng 1 ngày/tuần/người) cho "maintenance & refactor", ghi thẳng vào kế hoạch sprint như một hạng mục chính thức.
Trong ngày đó họ làm: sửa flaky, gom locator, xóa test chết, cập nhật test data. Sau 3 tháng, tỉ lệ pass ổn định của pipeline tăng từ 71% lên 94%, và tốc độ ra test mới thực tế tăng lên vì không còn bị test đỏ làm nghẽn.
Bài học: Bảo trì phải là một hạng mục có kế hoạch, được cấp thời gian tường minh, không phải việc "làm nếu còn dư giờ". Ngân sách 20% cho maintenance là con số khởi điểm hợp lý cho dự án đang ở năm 2; hãy nâng dần theo tuổi dự án.
Hướng dẫn từng bước
Đây là quy trình bạn có thể áp dụng để đưa ngân sách bảo trì và refactoring vào vận hành thực tế:
Bước 1 — Đo hiện trạng. Trước khi lập ngân sách, hãy biết bạn đang tốn bao nhiêu. Trong 2-3 sprint, gắn nhãn mọi task test theo ba xô: build / write / maintain. Cuối kỳ tính tỉ lệ phần trăm thực tế. Gần như chắc chắn maintain sẽ cao hơn bạn tưởng.
Bước 2 — Xác định giai đoạn vòng đời. Dự án của bạn đang ở năm mấy? Đối chiếu với khung 70/30/0 → 30/50/20 → 15/35/50 ở trên để biết tỉ lệ nên có. Khoảng cách giữa "đang có" và "nên có" chính là điều cần điều chỉnh.
Bước 3 — Đặt ngân sách bảo trì tường minh trong sprint. Ghi maintenance thành một hạng mục chính thức, ví dụ "20% năng lực sprint". Bảo vệ con số này như bảo vệ thời gian làm feature — không cho nó bị nuốt khi deadline gấp.
Bước 4 — Kiểm toán test suite định kỳ (mỗi quý). Với mỗi test, lấy từ CI hai số: tần suất flaky và số bug thật đã bắt. Lập ma trận: bắt bug cao + flaky thấp → giữ; bắt bug thấp + flaky cao → ứng viên xóa; bắt bug cao + flaky cao → ưu tiên refactor để ổn định.
Bước 5 — Refactor theo nguyên tắc "sửa một nơi". Bất cứ khi nào bạn phải sửa cùng một thứ ở nhiều hơn hai chỗ, dừng lại và trích xuất nó ra (Page Object, helper, hằng số). Áp dụng "boy scout rule": mỗi lần chạm vào một test để sửa, dọn sạch nó hơn một chút.
Bước 6 — Theo dõi chỉ số sức khỏe. Bốn chỉ số nên đưa lên dashboard: (1) tỉ lệ pass ổn định của pipeline, (2) thời gian chạy suite, (3) số test bị @Disabled/skip, (4) tỉ lệ flaky. Nếu (3) và (4) tăng dần, ngân sách bảo trì của bạn đang thiếu.
Bước 7 — Báo cáo bằng ngôn ngữ kinh doanh. Trình bày với quản lý: "Mỗi test tốn X giờ/năm để nuôi. Chúng ta có N test, vậy cần Y giờ/năm cho bảo trì." Biến chi phí ẩn thành con số trên bàn để được cấp nguồn lực chính đáng.
Lỗi thường gặp & mẹo
- Lỗi: Coi bảo trì là "việc phụ, làm khi rảnh". Kết cục là không bao giờ có lúc rảnh, test suite mục ruỗng. Mẹo: đưa maintenance vào Definition of Done và vào kế hoạch sprint như một hạng mục có giờ cụ thể.
- Lỗi: Chỉ thêm, không bao giờ xóa. Test suite phình mãi, chi phí bảo trì tăng tuyến tính theo số test. Mẹo: đặt lịch "spring cleaning" hàng quý, xóa test dựa trên dữ liệu bug-caught và flaky-rate. Một suite 400 test mạnh khỏe tốt hơn 1.200 test hấp hối.
- Lỗi: Vô hiệu hóa test đỏ bằng
@Disabled/skiprồi quên luôn. Đây là cách test suite chết dần trong im lặng. Mẹo: mọi test bị disable phải có ticket kèm hạn xử lý; test disable quá 2 sprint thì xóa hẳn hoặc sửa dứt điểm.
- Lỗi: Refactor test không có test bảo vệ. Bạn refactor rồi vô tình làm test luôn pass (false positive) mà không biết. Mẹo: sau khi refactor, cố tình "phá" sản phẩm một lần để chắc chắn test vẫn bắt được lỗi — kiểm tra rằng test còn khả năng fail.
- Lỗi: Đổ lỗi cho công cụ khi thực ra là do thiết kế. "Selenium flaky quá" thường thực chất là "wait strategy và locator của tôi tệ". Mẹo: trước khi đổi tool, hãy soi lại kiến trúc test — phần lớn chi phí bảo trì đến từ thiết kế kém chứ không từ công cụ.
- Mẹo vàng: Chi phí bảo trì tỉ lệ nghịch với chất lượng thiết kế ban đầu. Một giờ đầu tư vào Page Object và helper tốt tiết kiệm mười giờ bảo trì về sau. Đầu tư "phía trái" (khi viết) để rẻ "phía phải" (khi nuôi).
Bài tập thực hành
- Đo tỉ lệ ba xô của chính bạn. Lấy dự án hiện tại (hoặc một dự án mẫu), trong 2 sprint gắn nhãn mọi task thành build/write/maintain. Tính tỉ lệ và so sánh với khung vòng đời trong bài. Dự án của bạn đang ở "năm" nào và tỉ lệ có lệch không?
- Lập ma trận kiểm toán test. Chọn 20 test bất kỳ trong suite của bạn. Với mỗi test, tra lịch sử CI để điền hai cột: số lần flaky trong 3 tháng và số bug thật đã bắt. Sắp xếp vào 4 nhóm (giữ / xóa / refactor / theo dõi). Bạn xóa được bao nhiêu?
- Refactor một điểm đau. Tìm một selector hoặc thao tác (ví dụ đăng nhập) đang bị lặp ở ít nhất 3 test. Trích xuất nó ra Page Object hoặc helper. Đo số dòng code giảm và tưởng tượng: nếu UI đó đổi, trước đây sửa mấy chỗ, giờ sửa mấy chỗ?
- Ước tính ngân sách năm. Giả sử suite của bạn có N test, mỗi test trung bình tốn 1 giờ viết. Dùng quy tắc 10-20%/năm, tính khoảng giờ bảo trì cần thiết cho năm tới. Viết một đoạn 5 dòng trình bày con số này như thể báo cáo cho quản lý.
Tóm tắt
- Test suite có ba loại công việc — build, write, maintain — và tỉ lệ giữa chúng dịch chuyển theo tuổi dự án: năm 1 khoảng 70/30/0, năm 2 khoảng 30/50/20, năm 3+ khoảng 15/35/50. Với dự án trưởng thành, một nửa thời gian là bảo trì.
- Mỗi test là một khoản nợ định kỳ, tốn khoảng 10-20% thời gian viết ban đầu mỗi năm để nuôi. Thêm test phải cân nhắc chi phí lâu dài, không chỉ giá trị tức thời.
- Bảo trì phải là hạng mục có kế hoạch và được cấp giờ tường minh (khởi điểm ~20% năng lực sprint), không phải "việc làm khi rảnh".
- Refactoring test — gom locator, trích xuất helper, xóa test chết, đặt tên rõ — là cách kéo đường chi phí bảo trì từ dốc thành phẳng. Xóa test dựa trên dữ liệu (bug bắt được, tỉ lệ flaky) là một trong những hành động ROI cao nhất.
- Chi phí bảo trì tỉ lệ nghịch với chất lượng thiết kế ban đầu: đầu tư khi viết để rẻ khi nuôi. Và hãy luôn biến chi phí ẩn thành con số trên bàn để được cấp nguồn lực đúng mức.