[Bài dịch] Khi nào thì nên refactor, khi nào thì nên viết lại code cho automation test project

Một bài viết rất hay từ TestRigor, mình dịch lại qua tiếng Việt cho cả nhà dễ follow. Nó không chỉ đúng với Automation test code base, mà còn đúng cả với code của dev hay quá trình refactor/ rewriting nói chung nữa.
Chỉ trong sự phát triển, cải cách và thay đổi, thật nghịch lý, chúng ta mới tìm thấy sự an toàn thực sự – Anne Morrow Lindbergh.
Điều tương tự cũng đúng với phần mềm và các script test automation. Các test case automation đóng vai trò như tuyến phòng thủ trong việc đảm bảo chất lượng phần mềm. Tuy nhiên, người ta thường thấy mình vật lộn để duy trì chúng – bộ test suite từng sạch sẽ và hiệu quả giờ đây trở nên chậm chạp, không ổn định (flaky), hoặc khó bảo trì. Có thể các test case thất bại một cách ngẫu nhiên hoặc không ổn định, hoặc chúng không còn hiệu quả trong việc kiểm thử phiên bản phần mềm mới nhất.
Vào những lúc như vậy, người ta có thể tự hỏi: “Đã đến lúc refactor những gì bạn có, hay tốt hơn là viết lại từ đầu?”
Bài viết này sẽ cung cấp các tiêu chí rõ ràng và framework ra quyết định để giúp bạn chọn đúng hướng đi cho đội ngũ và sản phẩm của mình.
Điểm chính:
- Refactor khi các test đang hoạt động nhưng cần cải thiện tính bảo trì và hiệu suất
- Rewrite khi các test không còn đáp ứng business logic hoặc có thiết kế cơ bản bị lỗi
- Cân nhắc tác động lên quy trình phát triển và chi phí nguồn lực trước khi quyết định
- Refactor có thể được thực hiện dần dần với ít rủi ro hơn
- Rewrite cung cấp một khởi đầu mới nhưng đòi hỏi đầu tư thời gian đáng kể
Refactor vs. Rewrite
Khi nào nên Refactor?
Refactoring là việc làm cho các test hiện có của bạn hiệu quả hơn và dễ làm việc hơn, mà không thay đổi chức năng thực tế của chúng. Bạn đang cải thiện cấu trúc nội bộ của code, không phải xây lại ngôi nhà từ đầu.
Bạn nên cân nhắc refactoring khi các test của bạn đang hoạt động nhưng lộn xộn. Dưới đây là một số tình huống chính:
- Giá trị mang lại cao, công sức ít: Test hoạt động tốt, nhưng code khó hiểu hoặc cẩu thả. Bằng cách dành một chút thời gian dọn dẹp – có thể đổi tên biến hoặc tách một khối code thành method có thể tái sử dụng – bạn sẽ nhận được lợi ích lớn trong thời gian bảo trì tương lai.
- Code lộn xộn và Trùng lặp: Bạn có thấy cùng những dòng code được “copy paste” qua nhiều test không? Đó là một dấu hiệu đáng báo động. Refactoring cho phép bạn hợp nhất code thừa đó thành một function chung duy nhất. Điều này giúp suite của bạn dễ quản lý hơn và giảm nguy cơ lỗi. Một ví dụ tuyệt vời là giới thiệu Page Object Models để tập trung tất cả locators và actions cho một trang cụ thể.
- Optimize các đoạn logic đã biết: Có một số vấn đề chúng ta đã biết, nhưng nó đang chưa tốt, thì cần optimize. Ví dụ, bạn biết rằng một số test chậm vì chúng lặp lại quy trình đăng nhập không cần thiết. Bạn có thể refactor chúng để sử dụng lại login state.
- Khó đọc: Các test khó hiểu. Tên biến gây nhầm lẫn và không có comment để giải thích code làm gì. Refactoring giúp code trở nên trực quan hơn, để các thành viên mới trong nhóm có thể nhanh chóng nắm bắt.
- Technical Debt: Các test hoạt động, nhưng chúng được xây dựng với các bản code “tạm bợ”. Theo thời gian, “nợ” này làm chậm quá trình phát triển. Refactoring trả nợ đó bằng cách cải thiện thiết kế của code.
- Được sử dụng trong các Test Suite khác: Các test là một phần của regression suite quan trọng và nhìn chung đáng tin cậy, nhưng chúng đang trở thành vấn đề trong việc bảo trì. Chúng có thể không bị hỏng, nhưng độ phức tạp của chúng đang trở thành vấn đề ngày càng lớn. Refactoring có thể tháo gỡ độ phức tạp đó trước khi nó trở thành vấn đề nghiêm trọng.
- Thông tin sẵn có cho Refactoring: Nếu người tạo ban đầu của test vẫn còn làm việc, hoặc bạn có kiến thức domain cần thiết và tài liệu, thì refactor có xu hướng nhanh hơn.
Khi nào nên Rewrite?
Đôi khi, một bộ code base xấu tới mức…không thể refactor nổi. Dưới đây là các dấu hiệu chính cho thấy đã đến lúc cần một khởi đầu mới:
- Không đáp ứng nhu cầu business: Nếu các test của bạn vẫn chạy nhưng không còn kiểm thử đúng thứ, chúng vô dụng. Có thể sản phẩm đã phát triển, và các test cũ hiện đang nhắm vào các tính năng lỗi thời. Bạn đang có cảm giác an toàn “giả”, trong khi cần một test suite mới để bao phủ chức năng hiện tại.
- Flakiness không thể giải thích: Các test bị fail không có lý do rõ ràng là một sự tiêu tốn resource. Bạn và nhóm của bạn dành vô số giờ debug và chạy lại các test lẽ ra phải pass. Đây là triệu chứng của một vấn đề sâu hơn, mang tính hệ thống – như một framework được thiết kế kém hoặc phụ thuộc vào các component không ổn định. Tại thời điểm này, cố gắng sửa từng flaky test là một nước đi sai lầm.
- Kiến trúc bị hỏng: Khi thiết kế của test suite có lỗi cơ bản, không thể sửa chữa được. Ví dụ, nếu các test của bạn quá phụ thuộc vào UI, một thay đổi nhỏ trên webpage có thể làm hỏng hàng tá test. Rewrite cho phép bạn xây dựng một framework mạnh mẽ hơn, tách rời từ đầu.
- Chuyển đổi công nghệ: Nếu toàn bộ framework phát triển hoặc testing của bạn đã thay đổi, test suite cũ về cơ bản đã lỗi thời. Cố gắng migrate các test cũ sang công nghệ mới thường tốn công hơn việc đơn giản là viết mới.
Ưu và nhược điểm của Refactoring
Lợi ích của Refactoring
- Nhanh hơn và ít rủi ro hơn: Refactoring thường ít tốn thời gian và gây gián đoạn hơn rewriting. Bạn có thể làm dần dần, từng file hoặc từng function. Điều này giữ nguyên test coverage của bạn và giảm rủi ro tạo ra bug mới, vì bạn không thay đổi chức năng cốt lõi của các test.
- Cải thiện khả năng bảo trì: Bằng cách dọn dẹp code lộn xộn, refactoring làm cho test suite của bạn dễ hiểu và quản lý hơn. Bạn có thể loại bỏ code trùng lặp, đặt tên biến rõ ràng và giới thiệu các design pattern. Điều này đơn giản hóa các thay đổi trong tương lai và giảm nỗ lực cần thiết để sửa hoặc cập nhật test.
- Giảm Technical Debt: Mỗi khi bạn thêm một bản sửa nhanh hoặc giải pháp không lý tưởng, bạn tạo ra technical debt. Refactoring là cách bạn trả lại nợ đó. Nó cho phép bạn giải quyết code smell và sự kém hiệu quả, dẫn đến một codebase khỏe mạnh hơn, bền vững hơn về lâu dài.
- Giữ lại các logic đặc biệt: Không giống như rewrite, refactoring bảo tồn kiến thức và logic được nhúng trong các test hiện có của bạn. Bạn không phải bắt đầu từ đầu, có nghĩa là bạn sẽ không mất coverage cho các tính năng khó hiểu hoặc hiếm khi sử dụng có thể đã bị quên.
Thách thức trong Refactoring
- Tác động hạn chế đến thiết kế: Nếu thiết kế ban đầu của test suite có lỗi cơ bản – ví dụ, nó quá phụ thuộc vào UI hoặc dựa vào framework lỗi thời – refactoring sẽ không sửa được các vấn đề kiến trúc cơ bản. Bạn có thể làm cho nó đẹp hơn, nhưng bạn không thể sửa một nền tảng bị hỏng.
- Rủi ro tạo ra Bug: Mặc dù mục tiêu không phải là thay đổi logic, refactoring vẫn có thể tạo ra các bug. Nếu một thay đổi không được xử lý cẩn thận, nó có thể làm hỏng test hoặc, tệ hơn, làm test pass khi lẽ ra nó phải fail.
- Cam kết thời gian cho ít thay đổi có thể nhìn thấy: Từ góc độ quản lý dự án, refactoring có thể khó thuyết phục. Nó đòi hỏi thời gian và nỗ lực đáng kể, nhưng kết quả cuối cùng không thêm tính năng mới hoặc trực tiếp sửa bug trong sản phẩm. Giá trị nằm ở tương lai, điều này không phải lúc nào cũng dễ biện minh.
- Có thể là nhiệm vụ không bao giờ kết thúc: Nếu test suite ở trạng thái tồi, refactoring có thể cảm thấy như một hố không đáy. Bạn sửa một khu vực chỉ để tìm thấy vấn đề khác ngay phía sau. Có thể khó biết khi nào nên dừng, và nó có thể không phải là cách sử dụng thời gian hiệu quả nhất của nhóm nếu các vấn đề quá lan rộng.
Ưu và nhược điểm của Rewriting
Lợi ích của Rewriting
- Một trang giấy trắng: Bạn có thể bắt đầu mới mà không bị kìm hãm bởi codebase cũ. Bạn có thể triển khai các best practice mới, sử dụng các công cụ mới nhất, và tránh các quyết định kém trong quá khứ. Điều này dẫn đến một suite hiệu quả hơn, có thể mở rộng và dễ bảo trì hơn.
- Cải thiện độ tin cậy: Nếu các test cũ của bạn flaky và không đáng tin cậy, rewrite là cách nhanh nhất để xây dựng một mạng lưới an toàn đáng tin cậy cho sản phẩm của bạn. Bạn có thể thiết kế các test ổn định ngay từ đầu, sử dụng locator tốt hơn, điều kiện chờ mạnh mẽ hơn, và code ít phụ thuộc hơn.
- Tăng tinh thần: Làm việc với test suite lộn xộn, không đáng tin cậy có thể làm giảm tinh thần của nhóm. Rewrite cung cấp một khởi đầu mới và cơ hội xây dựng thứ gì đó mà nhóm có thể tự hào.
- Phù hợp với công nghệ mới: Rewriting cho phép bạn xây dựng test suite phù hợp hoàn hảo với stack công nghệ mới, đảm bảo automation của bạn phù hợp và hiệu quả.
Thách thức trong Rewriting
- Thời gian và nỗ lực đáng kể: Rewriting đòi hỏi đầu tư thời gian lớn, có thể chuyển hướng nguồn lực từ các nhiệm vụ quan trọng khác. Bạn sẽ cần phân tích lại yêu cầu, thiết kế lại test case, và triển khai lại một số lượng lớn test.
- Tăng rủi ro trong ngắn hạn: Trong quá trình rewrite, bạn sẽ có một giai đoạn mà test coverage của bạn không hoàn chỉnh. Bạn sẽ mất mạng lưới an toàn của các test cũ trước khi các test mới được xây dựng đầy đủ và đáng tin cậy. Điều này có thể khiến sản phẩm của bạn dễ bị bug và tăng rủi ro regression đi vào production.
- Khả năng lặp lại sai lầm trong quá khứ: Không có kế hoạch rõ ràng, rewrite có thể dẫn đến cùng vấn đề mà bạn đang cố gắng thoát khỏi. Nếu bạn không giải quyết nguyên nhân gốc rễ của thất bại của suite ban đầu, bạn có thể chỉ kết thúc với một bộ test mới lộn xộn, flaky trong tương lai.
- Mất các logic đang có: Trong khi một số test cũ vô dụng, những test khác có thể chứa những hiểu biết và logic có giá trị có thể bị mất trong quá trình rewrite. Test suite cũ, dù có nhiều lỗi, có thể đã bao phủ các edge case phức tạp có thể bị quên khi bắt đầu từ đầu.
Mẹo cho việc Rewriting hoặc Refactoring hiệu quả
Dù bạn đang thực hiện rewrite hoàn toàn hay refactor chiến lược, cả hai nhiệm vụ đều đòi hỏi cách tiếp cận chu đáo để thành công. Đây là một số mẹo để hướng dẫn bạn qua quá trình, đảm bảo nỗ lực của bạn dẫn đến một test suite đáng tin cậy và dễ bảo trì hơn.
Mẹo cho Refactoring
Làm nhỏ và dần dần
Đừng cố gắng refactor mọi thứ cùng một lúc. Thay vào đó, chọn một khu vực nhỏ, độc lập để bắt đầu – có thể là một flaky test duy nhất hoặc một function chung được sử dụng bởi nhiều test. Thực hiện thay đổi, chạy test để đảm bảo không có gì bị hỏng, sau đó chuyển sang khu vực tiếp theo. Điều này làm cho quá trình có thể quản lý được và ít có khả năng tạo ra lỗi.
Tuân theo “Boy Scout Rule”
Boy Scout rule rất đơn giản: Luôn để code sạch hơn bạn tìm thấy nó. Mỗi khi bạn chạm vào test vì bất kỳ lý do gì, hãy dành một chút thời gian để cải thiện nó. Có thể bạn có thể đổi tên một biến khó hiểu, loại bỏ một dòng code trùng lặp, hoặc thêm comment hữu ích. Thực hành này, theo thời gian, có thể cải thiện đáng kể sức khỏe của test suite mà không cần dự án refactoring chuyên dụng.
Sử dụng Design Pattern
Nếu các test của bạn lộn xộn, thiếu thiết kế nhất quán có thể là một phần lớn của vấn đề. Điều này tách logic test của bạn khỏi chi tiết của trang web, làm cho test của bạn dễ đọc và bảo trì hơn. Ví dụ, nếu ID của button thay đổi, bạn chỉ cần cập nhật nó ở một nơi trong POM của bạn, không phải qua hàng tá test khác nhau.
Mẹo cho Rewriting
Tạo kế hoạch rõ ràng
Rewrite là một dự án lớn, vì vậy đừng chỉ nhảy vào. Bắt đầu bằng cách xác định mục tiêu của test suite mới.
Ghi lại kế hoạch của bạn và nhận được sự đồng thuận từ nhóm của bạn. Lộ trình này rất quan trọng để duy trì đúng hướng và đo lường thành công của bạn.
Xây dựng hệ thống song song
Bạn không thể chỉ dừng testing trong khi bạn rewrite toàn bộ suite. Xây dựng test suite mới song song với cái cũ.
Cách tiếp cận “chạy song song” này cho phép bạn dần dần migrate test từ hệ thống cũ sang hệ thống mới. Điều này duy trì test coverage và đảm bảo bạn có mạng lưới an toàn trong khi bạn xây dựng suite mới.
Tập trung vào các Test có giá trị cao trước
Khi bạn bắt đầu rewriting, ưu tiên các test quan trọng nhất. Đây là những test bao phủ các tính năng kinh doanh cốt lõi hoặc có lịch sử bắt các bug quan trọng. Đưa các test có giá trị cao này vào suite mới nhanh chóng cung cấp giá trị ngay lập tức và giảm rủi ro. Đừng lo lắng về việc rewrite mọi test cũ; một số có thể không cần thiết nữa.
Học từ quá khứ
Trước khi bạn bắt đầu viết test mới, hãy dành thời gian phân tích tại sao những test cũ thất bại.
Chúng có quá phụ thuộc vào UI không?
Chúng có quá phụ thuộc vào dữ liệu bên ngoài không? Sử dụng những bài học này để thông báo cho thiết kế của suite mới. Rewrite không chỉ là viết script mới – đó là xây dựng một hệ thống tốt hơn.
Kết luận
Việc chọn refactor hay rewrite phụ thuộc vào một quy tắc đơn giản: refactor khi phần cốt lõi vẫn tốt nhưng lộn xộn, và rewrite khi nền tảng bị hỏng và không thể tin cậy được. Đây thường là một bài tập liên tục, nơi bạn có thể cần định kỳ quay lại và đánh giá sức khỏe của test suite. Quá trình có thể ít choáng ngợp hơn nếu nó được thực hiện thường xuyên và theo từng đợt nhỏ.
