Chào bạn, nhà phát triển đồng nghiệp! Nếu bạn đã từng làm việc với kiểm thử tự động, bạn sẽ biết cảm giác chán nản khi thấy một bài kiểm thử thất bại mặc dù không có gì thay đổi trong mã. Hãy đặt một bối cảnh, tôi cá là nó quá quen thuộc. Bạn đẩy đoạn mã được tạo tác đẹp đẽ của mình, tự tin rằng đó là tác phẩm tốt nhất của bạn từ trước đến nay. Bạn kích hoạt quy trình tích hợp liên tục (CI) và chờ đợi dấu kiểm màu xanh lá cây đầy thỏa mãn. Nhưng thay vào đó, bạn nhận được một dấu X màu đỏ lớn, giận dữ. Tim bạn thắt lại. "Mình đã làm hỏng cái gì vậy?!" Bạn vội vã kiểm tra nhật ký, chỉ để tìm thấy... một lỗi kiểm thử ngẫu nhiên. Bạn chạy lại nó: đôi khi nó vượt qua, đôi khi không.
Nghe quen không? Bạn, bạn của tôi, vừa trở thành nạn nhân của một bài kiểm thử không ổn định (flaky test).
Và đây là sự thật: các bài kiểm thử không ổn định làm lãng phí thời gian của nhà phát triển, làm chậm các quy trình CI/CD và gây ra sự thất vọng lớn cho các nhóm. Các bài kiểm thử không ổn định là những bóng ma ám ảnh của quá trình phát triển phần mềm. Chúng thất bại một cách không thể đoán trước và dường như ngẫu nhiên, làm xói mòn niềm tin vào toàn bộ quy trình kiểm thử của bạn, lãng phí vô số giờ điều tra và làm chậm quá trình giao hàng đến mức bò. Trên thực tế, chúng là một vấn đề nhức nhối phổ biến đến mức các nhà lãnh đạo ngành như Google đã công bố nghiên cứu sâu rộng về việc loại bỏ chúng.
Nhưng đây là tin tốt: các bài kiểm thử không ổn định không phải là phép thuật. Chúng có những nguyên nhân cụ thể, có thể xác định được. Và cái gì có thể xác định được thì có thể sửa được. Bạn có thể giải quyết chúng một khi bạn hiểu nguyên nhân gốc rễ của chúng.
Bạn muốn một nền tảng tích hợp, Tất cả trong một để Nhóm phát triển của bạn làm việc cùng nhau với năng suất tối đa?
Apidog đáp ứng mọi yêu cầu của bạn và thay thế Postman với mức giá phải chăng hơn nhiều!
Chính xác thì Kiểm thử không ổn định là gì?
Trước khi chúng ta liệt kê các thủ phạm, hãy định nghĩa kẻ thù của chúng ta. Một bài kiểm thử không ổn định là một bài kiểm thử thể hiện cả hành vi vượt qua và thất bại khi chạy nhiều lần trên cùng một phiên bản mã. Đó không phải là một bài kiểm thử thất bại nhất quán vì một lỗi. Đó là một bài kiểm thử thất bại không nhất quán, khiến nó trở thành một chỉ số ồn ào và không đáng tin cậy về tình trạng mã.
Ví dụ:
- Lần chạy #1 → ✅ Vượt qua
- Lần chạy #2 → ❌ Thất bại
- Lần chạy #3 → ✅ Vượt qua lần nữa
Chi phí của những bài kiểm thử này là rất lớn. Chúng dẫn đến:
- Chu kỳ "Chạy lại và Cầu nguyện": Lãng phí tài nguyên của nhà phát triển và CI.
- Mệt mỏi vì cảnh báo: Khi các bài kiểm thử thường xuyên thất bại mà không có lý do, các nhóm bắt đầu bỏ qua các lỗi, điều này có nghĩa là các lỗi thực sự bị bỏ sót.
- Tốc độ phát triển chậm hơn: Các bản dựng bị hỏng và thời gian điều tra làm chậm toàn bộ nhóm.
Tại sao các bài kiểm thử không ổn định lại nguy hiểm cho các nhóm
Bạn có thể nghĩ, "Chỉ là một bài kiểm thử thất bại, tôi sẽ chạy lại nó." Nhưng đây là vấn đề:
- Mất niềm tin → Các nhà phát triển ngừng tin tưởng vào kết quả kiểm thử.
- CI/CD chậm hơn → Các quy trình bị tắc nghẽn bởi các lần thử lại.
- Lỗi ẩn → Các vấn đề thực sự bị bỏ qua vì mọi người cho rằng "ồ, nó chỉ là không ổn định thôi."
- Tăng chi phí → Nhiều lần chạy lại hơn có nghĩa là nhiều thời gian, tài nguyên và cơ sở hạ tầng hơn.
Theo các nghiên cứu trong ngành, một số công ty dành tới 40% thời gian kiểm thử để xử lý sự không ổn định. Con số đó là rất lớn!
Bây giờ, hãy gặp gỡ những kẻ tình nghi thông thường.
Các nguyên nhân và cách khắc phục các bài kiểm thử không ổn định
1. Các thao tác không đồng bộ và tình trạng tranh chấp
Đây có lẽ là vua của các bài kiểm thử không ổn định. Trong các ứng dụng hiện đại, mọi thứ đều là các lệnh gọi API không đồng bộ, các thao tác cơ sở dữ liệu, cập nhật giao diện người dùng. Nếu bài kiểm thử của bạn không đợi đúng cách các thao tác này hoàn thành, về cơ bản nó đang đoán mò. Đôi khi nó đoán đúng (thao tác hoàn thành nhanh), và đôi khi nó đoán sai (nó chậm), dẫn đến thất bại.
Tại sao nó xảy ra: Mã kiểm thử của bạn thực thi đồng bộ, nhưng mã ứng dụng mà nó đang kiểm thử thì không.
Ví dụ: Một bài kiểm thử nhấp vào nút "Lưu" và ngay lập tức kiểm tra cơ sở dữ liệu để tìm bản ghi mới mà không đợi yêu cầu mạng của thao tác lưu hoàn thành.
Cách khắc phục:
- Sử dụng thời gian chờ rõ ràng: Không bao giờ sử dụng các lệnh gọi
sleep()hoặcsetTimeout()tĩnh. Đây là nguồn chính gây ra sự không ổn định vì bạn đang đợi quá lâu (làm chậm các bài kiểm thử) hoặc không đủ lâu (gây ra lỗi). - Áp dụng các chiến lược chờ đợi: Sử dụng các công cụ cho phép bạn đợi một điều kiện cụ thể. Ví dụ:
- Kiểm thử giao diện người dùng: Đợi một phần tử hiển thị, có thể nhấp hoặc chứa văn bản cụ thể.
- Kiểm thử API: Đợi một trạng thái phản hồi HTTP cụ thể hoặc một tải trọng xuất hiện trong cơ sở dữ liệu.
- Selenium/WebDriver: Sử dụng
WebDriverWaitvớiexpected_conditions. - Cypress: Cypress có tính năng chờ tự động được tích hợp sẵn cho hầu hết các lệnh, điều này rất tuyệt vời để tránh vấn đề này.
2. Các vấn đề về cô lập kiểm thử
Các bài kiểm thử nên giống như những người lạ lịch sự: chúng không nên để lại mớ hỗn độn cho người tiếp theo. Khi các bài kiểm thử chia sẻ trạng thái và không dọn dẹp sau khi chạy, chúng có thể dễ dàng can thiệp lẫn nhau. Bài kiểm thử A tạo một người dùng "test@example.com", vượt qua, nhưng không xóa nó. Bài kiểm thử B sau đó cố gắng tạo cùng một người dùng và thất bại vì vi phạm ràng buộc duy nhất.
Tại sao nó xảy ra: Các tài nguyên được chia sẻ như cơ sở dữ liệu, bộ nhớ đệm hoặc hệ thống tệp bị một bài kiểm thử sửa đổi, làm thay đổi trạng thái bắt đầu cho bài kiểm thử tiếp theo.
Cách khắc phục:
- Đảm bảo cô lập hoàn toàn: Mỗi bài kiểm thử nên thiết lập dữ liệu cần thiết của riêng nó và loại bỏ hoàn toàn sau đó. Đây là quy tắc vàng.
- Sử dụng giao dịch: Một mẫu mạnh mẽ là chạy mỗi bài kiểm thử bên trong một giao dịch cơ sở dữ liệu và sau đó hoàn tác nó ở cuối. Điều này làm cho cơ sở dữ liệu hoàn toàn nguyên vẹn.
- Tạo dữ liệu duy nhất: Sử dụng các định danh duy nhất (như UUID hoặc dấu thời gian) trong dữ liệu kiểm thử để tránh xung đột. Ví dụ:
test.user.<timestamp>@example.com.
3. Phụ thuộc vào dịch vụ bên ngoài
Bộ kiểm thử của bạn có gọi một API của bên thứ ba để xử lý thanh toán, dữ liệu thời tiết hoặc xác thực email không? Nếu có, bạn đã tạo ra một điểm lỗi lớn hoàn toàn nằm ngoài tầm kiểm soát của bạn. API đó có thể chậm, giới hạn tốc độ của bạn, ngừng hoạt động để bảo trì hoặc đã thay đổi định dạng phản hồi một chút tất cả những điều này sẽ làm cho các bài kiểm thử của bạn thất bại mà không phải do lỗi của bạn.
Tại sao nó xảy ra: Thành công của bài kiểm thử được gắn với tình trạng và hiệu suất của một hệ thống bên ngoài.
Cách khắc phục:
- Giả lập và tạo Stub cho dịch vụ bên ngoài: Đây là chiến lược quan trọng nhất. Thay vì thực hiện một lệnh gọi HTTP thực, hãy chặn yêu cầu và trả về một phản hồi giả, được xác định trước mô phỏng một trường hợp thành công hoặc lỗi.
- Sử dụng công cụ để giả lập: Đây là nơi Apidog tỏa sáng. Apidog cho phép bạn tạo các giả lập mạnh mẽ cho API của bạn. Bạn có thể xác định chính xác phản hồi mà API nên trả về cho một yêu cầu nhất định, loại bỏ hoàn toàn sự phụ thuộc vào dịch vụ bên ngoài thực, không ổn định. Các bài kiểm thử của bạn trở nên nhanh chóng, đáng tin cậy và có thể dự đoán được.
- Sử dụng ảo hóa dịch vụ: Đối với các kịch bản phức tạp hơn, có thể sử dụng các công cụ mô phỏng hành vi của toàn bộ hệ thống bên ngoài.
4. Dữ liệu kiểm thử không được quản lý
Tương tự như các vấn đề cô lập, nhưng rộng hơn. Nếu các bài kiểm thử của bạn giả định một trạng thái cụ thể của cơ sở dữ liệu (ví dụ: "phải có chính xác 5 người dùng" hoặc "một sản phẩm có ID 123 phải tồn tại"), chúng sẽ thất bại ngay khi giả định đó sai. Điều này thường xảy ra với các bài kiểm thử chạy trên một cơ sở dữ liệu phát triển hoặc dàn dựng được chia sẻ liên tục thay đổi.
Tại sao nó xảy ra: Các bài kiểm thử đưa ra các giả định ngầm về trạng thái dữ liệu của môi trường.
Cách khắc phục:
- Quản lý rõ ràng tất cả dữ liệu: Một bài kiểm thử không bao giờ nên giả định bất cứ điều gì về thế giới. Nó nên tạo tất cả dữ liệu cần thiết để chạy.
- Sử dụng Factory và Fixture: Các thư viện như
factory_bot(Ruby) hoặc các mẫu tương tự trong các ngôn ngữ khác giúp bạn dễ dàng tạo dữ liệu chính xác cần thiết cho mỗi bài kiểm thử. - Tránh các ID được mã hóa cứng: Không bao giờ dựa vào một ID bản ghi cụ thể tồn tại. Tạo bản ghi và sử dụng ID được tạo của nó trong các xác nhận kiểm thử của bạn.
5. Đồng thời và thực thi kiểm thử song song
Chạy các bài kiểm thử song song là điều cần thiết để tăng tốc độ. Tuy nhiên, nếu các bài kiểm thử của bạn không được thiết kế cho điều đó, chúng sẽ giẫm đạp lên nhau. Hai bài kiểm thử chạy cùng lúc có thể cố gắng truy cập cùng một tệp, sử dụng cùng một cổng trên máy chủ cục bộ hoặc sửa đổi cùng một bản ghi cơ sở dữ liệu.
Tại sao nó xảy ra: Các bài kiểm thử được thực thi đồng thời nhưng được viết với giả định rằng chúng sẽ chạy một mình.
Cách khắc phục:
- Thiết kế cho song song hóa ngay từ đầu: Giả định các bài kiểm thử sẽ chạy song song.
- Cô lập tài nguyên: Đảm bảo mỗi trình chạy kiểm thử song song có môi trường riêng biệt: một lược đồ cơ sở dữ liệu duy nhất, một cổng duy nhất cho các máy chủ cục bộ, v.v.
- Sử dụng các thao tác an toàn theo luồng: Lưu ý bất kỳ trạng thái trong bộ nhớ được chia sẻ nào.
6. Phụ thuộc vào thời gian hệ thống
"Bài kiểm thử này có thất bại sau 5 giờ chiều không?" Nghe có vẻ ngớ ngẩn, nhưng nó xảy ra. Các bài kiểm thử sử dụng thời gian hệ thống thực (new Date(), DateTime.Now) có thể hoạt động khác nhau tùy thuộc vào thời điểm chúng được chạy. Một bài kiểm thử kiểm tra xem "báo cáo hàng ngày" có được tạo hay không có thể vượt qua khi chạy một lần lúc 11:59 tối và sau đó thất bại khi chạy lại hai phút sau đó lúc 12:01 sáng.
Tại sao nó xảy ra: Đồng hồ hệ thống là một đầu vào bên ngoài, thay đổi.
Cách khắc phục:
- Giả lập thời gian: Sử dụng các thư viện cho phép bạn "đóng băng" hoặc "du hành" thời gian. Các thư viện như
timecop(Ruby),freezegun(Python) hoặcmockStaticcủaMockitochojava.time(Java) cho phép bạn đặt một thời gian cụ thể cho bài kiểm thử của mình, làm cho nó hoàn toàn xác định.
7. Mã không xác định trong các bài kiểm thử
Điều này rất tinh tế. Nếu mã đang được kiểm thử là không xác định (ví dụ: nó sử dụng bộ tạo số ngẫu nhiên hoặc xáo trộn một danh sách), bài kiểm thử của bạn không thể đưa ra một xác nhận nhất quán về đầu ra của nó.
Tại sao nó xảy ra: Bản thân logic ứng dụng có tính ngẫu nhiên.
Cách khắc phục:
- Gieo hạt bộ tạo số ngẫu nhiên: Hầu hết các bộ tạo số ngẫu nhiên có thể được gieo hạt với một giá trị cố định. Điều này làm cho chuỗi số "ngẫu nhiên" giống hệt nhau mỗi lần, làm cho bài kiểm thử trở nên xác định.
- Kiểm thử hành vi, không phải triển khai: Thay vì xác nhận đầu ra chính xác của một hàm
shuffle()(theo định nghĩa là ngẫu nhiên), hãy xác nhận hành vi. Ví dụ, xác nhận rằng danh sách đầu ra chứa tất cả các phần tử giống như danh sách đầu vào, chỉ khác thứ tự. Hoặc, giả lập hàm shuffle để trả về một thứ tự cố định trong quá trình kiểm thử.
8. Bộ chọn giao diện người dùng dễ vỡ
Đây là sự không ổn định kiểm thử giao diện người dùng kinh điển. Bài kiểm thử của bạn tìm một phần tử trên trang bằng cách sử dụng bộ chọn CSS như #main > div > div > div:nth-child(3) > button. Một nhà phát triển sau đó điều chỉnh nhẹ cấu trúc HTML thêm một div mới để tạo kiểu và bùm, bộ chọn của bạn bị hỏng, mặc dù chức năng hoàn toàn ổn.
Tại sao nó xảy ra: Các bộ chọn quá gắn chặt với cấu trúc DOM, vốn không ổn định.
Cách khắc phục:
- Sử dụng bộ định vị mạnh mẽ: Ưu tiên các bộ chọn ít có khả năng thay đổi.
- Tốt nhất: Sử dụng thuộc tính
data-testidchuyên dụng (ví dụ:<button data-testid="sign-up-button">). Điều này tách biệt kiểm thử khỏi kiểu dáng và cấu trúc. - Tốt: Sử dụng ID (
#submit-button), nhưng chỉ khi chúng ổn định và không được sử dụng cho CSS. - Ổn: Sử dụng vai trò ARIA hoặc nội dung văn bản, nhưng hãy cẩn thận với việc quốc tế hóa và thay đổi văn bản.
- Tránh: Các đường dẫn CSS/XPath phức tạp, lồng nhau dựa trên cấu trúc.
9. Rò rỉ tài nguyên và lỗi dọn dẹp
Các bài kiểm thử không đóng đúng cách các tài nguyên có thể khiến các bài kiểm thử tiếp theo thất bại theo những cách kỳ lạ. Điều này có thể là để lại các kết nối cơ sở dữ liệu mở, không đóng các phiên bản trình duyệt hoặc không xóa các tệp tạm thời. Cuối cùng, hệ thống hết tài nguyên, gây ra thời gian chờ hoặc sự cố.
Tại sao nó xảy ra: Mã kiểm thử không có logic dọn dẹp/hủy bỏ thích hợp.
Cách khắc phục:
- Sử dụng Hooks
beforeEach/afterEach: Cấu trúc các bài kiểm thử của bạn để luôn dọn dẹp trong một giai đoạn hủy bỏ chuyên dụng, ngay cả khi bài kiểm thử thất bại. Hầu hết các framework kiểm thử đều cung cấp các hook cho việc này. - Áp dụng các mẫu phù hợp: Sử dụng các mẫu như câu lệnh
using(C#) hoặctry-with-resources(Java) để đảm bảo các tài nguyên được tự động đóng.
10. Không nhất quán môi trường
"Bài kiểm thử hoạt động trên máy của tôi!" Lời kêu gọi kinh điển này thường do sự không ổn định của môi trường. Sự khác biệt về hệ điều hành, phiên bản trình duyệt, phiên bản Node.js, thư viện đã cài đặt hoặc biến môi trường giữa máy cục bộ của nhà phát triển và máy chủ CI có thể khiến các bài kiểm thử thất bại một cách không thể đoán trước.
Tại sao nó xảy ra: Môi trường kiểm thử không thể tái tạo.
Cách khắc phục:
- Đóng gói mọi thứ: Sử dụng Docker để định nghĩa môi trường kiểm thử của bạn. Một
Dockerfileđảm bảo rằng mọi lần chạy kiểm thử cục bộ và CI đều diễn ra trong một môi trường giống hệt nhau, được kiểm soát. - Ghim phiên bản mọi thứ: Sử dụng
package-lock.json,Gemfile.lock,Pipfile.lock, v.v., để khóa các phiên bản chính xác của tất cả các phụ thuộc của bạn. - Quản lý cấu hình an toàn: Sử dụng một phương pháp nhất quán và an toàn để xử lý các biến môi trường và bí mật cần thiết cho việc kiểm thử.
Cách phát hiện các bài kiểm thử không ổn định trong quy trình của bạn
Phát hiện sớm các bài kiểm thử không ổn định là chìa khóa. Dưới đây là các chiến lược:
- Chạy lại các bài kiểm thử tự động → Nếu một bài kiểm thử vượt qua sau khi chạy lại, hãy đánh dấu nó là không ổn định.
- Theo dõi các mẫu lỗi → Nhật ký CI/CD thường tiết lộ các bài kiểm thử không ổn định lặp lại.
- Cô lập các bài kiểm thử không ổn định → Gắn thẻ chúng và chạy riêng cho đến khi được sửa.
- Sử dụng công cụ giám sát → Các công cụ như Jenkins, CircleCI và GitHub Actions có thể báo cáo sự không ổn định của kiểm thử.
Giảm thiểu các bài kiểm thử không ổn định với Apidog

Vì nhiều bài kiểm thử không ổn định liên quan đến API và các phụ thuộc bên ngoài, Apidog giúp bạn:
- Tạo máy chủ giả lập để bạn không phụ thuộc vào các API thực không ổn định.
- Tự động hóa các kịch bản kiểm thử với kết quả có thể dự đoán được.
- Chạy các bài kiểm thử hiệu suất để xem API hoạt động như thế nào dưới áp lực.
- Tập trung tất cả các bài kiểm thử API của bạn để bạn có thể phát hiện hành vi không ổn định sớm.
Thay vì gỡ lỗi các lỗi ngẫu nhiên lúc 2 giờ sáng, bạn sẽ biết chính xác đó là mã của bạn hay một phụ thuộc bên ngoài không ổn định.
Các phương pháp hay nhất để tránh các bài kiểm thử không ổn định
Dưới đây là danh sách kiểm tra nhanh để giảm sự không ổn định của kiểm thử:
- Viết các bài kiểm thử xác định với kết quả có thể dự đoán được.
- Sử dụng giả lập/stub cho API và mạng.
- Tránh các độ trễ được mã hóa cứng, sử dụng các thời gian chờ dựa trên sự kiện.
- Đặt lại môi trường kiểm thử giữa các lần chạy.
- Giám sát các bài kiểm thử theo thời gian để phát hiện các mẫu không ổn định.
- Tài liệu hóa các bài kiểm thử không ổn định đã biết để nhóm nắm được.
Xây dựng văn hóa chống lại sự không ổn định
Sửa chữa các bài kiểm thử riêng lẻ là một chuyện; ngăn chặn chúng là một chuyện khác. Nó đòi hỏi một văn hóa nhóm coi trọng độ tin cậy của kiểm thử.
- Không dung thứ cho sự không ổn định: Nếu một bài kiểm thử không ổn định, hãy cách ly nó ngay lập tức. Di chuyển nó đến một bộ riêng biệt, không chặn để nó không chặn việc triển khai, nhưng lên lịch thời gian để sửa nó càng sớm càng tốt.
- Theo dõi các bài kiểm thử không ổn định: Giữ một danh sách hiển thị các bài kiểm thử không ổn định đã biết và ưu tiên sửa chữa chúng.
- Xem xét các bài kiểm thử trong các đánh giá mã: Coi mã kiểm thử nghiêm túc như mã sản xuất. Tìm kiếm các mẫu phản đối mà chúng ta đã thảo luận trong quá trình đánh giá.
Kết luận: Từ không ổn định đến mạnh mẽ
Các bài kiểm thử không ổn định là một trong những vấn đề gây khó chịu nhất trong phát triển phần mềm, chúng là một phiền toái, nhưng chúng có thể giải quyết được. Chúng làm lãng phí thời gian, tạo ra sự mất lòng tin và làm chậm quá trình phát hành. Bằng cách hiểu 10 nguyên nhân hàng đầu này từ các thời gian chờ không đồng bộ và cô lập kiểm thử đến các giả lập bên ngoài và các bộ chọn dễ vỡ, bạn có được sức mạnh không chỉ để sửa chữa chúng mà còn để viết các bài kiểm thử mạnh mẽ, đáng tin cậy hơn ngay từ đầu, bạn có thể sửa chữa chúng một cách có hệ thống.
Hãy nhớ rằng, một bộ kiểm thử là một hệ thống cảnh báo sớm quan trọng cho tình trạng ứng dụng của bạn. Giá trị của nó tỷ lệ thuận với sự tin tưởng mà nhóm phát triển có vào nó. Bằng cách loại bỏ triệt để sự không ổn định, bạn xây dựng lại niềm tin đó và tạo ra một quy trình phát triển nhanh hơn, tự tin hơn. Chiến lược tốt nhất? Thiết kế các bài kiểm thử xác định, cô lập và có cấu trúc tốt.
Và đối với những sự không ổn định liên quan đến API đặc biệt khó khăn đó, hãy nhớ rằng một công cụ như Apidog có thể là đồng minh mạnh nhất của bạn. Các khả năng giả lập và kiểm thử của nó được thiết kế đặc biệt để tạo ra môi trường ổn định, có thể dự đoán được mà các bài kiểm thử của bạn cần để phát triển. Apidog có thể cứu bạn khỏi một thế giới đau khổ vì kiểm thử không ổn định bằng cách mô phỏng các môi trường ổn định. Bây giờ hãy tiến lên và làm cho bộ kiểm thử của bạn không thể phá vỡ.
