Khi tôi xem lại một bộ phim mà tôi yêu thích khi còn nhỏ, có một câu trích dẫn đặc biệt nổi bật. Đó là từ bộ phim Star Wars năm 1983 “Return of the Jedi”. Câu này được nói trong Trận chiến Endor, nơi Liên minh huy động lực lượng của mình trong nỗ lực tập trung để phá hủy Ngôi sao Chết. Ở đó, Đô đốc Ackbar, thủ lĩnh của quân nổi loạn Mon Calamari, đã nói câu đáng nhớ của mình:

“Đó là một cái bẫy!” Dòng này cảnh báo chúng ta về một cuộc phục kích bất ngờ, một mối nguy hiểm sắp xảy ra. Được rồi, nhưng điều này liên quan gì đến việc thử nghiệm? Vâng, nó chỉ đơn giản là một ẩn dụ thích hợp khi nói đến việc xử lý các bài kiểm tra trong cơ sở mã. Những cạm bẫy này có thể giống như một cuộc phục kích bất ngờ khi bạn làm việc trên cơ sở mã, đặc biệt là khi làm việc trong thời gian dài.
Trong bài viết này, tôi sẽ cho bạn biết những cạm bẫy mà tôi đã gặp phải trong sự nghiệp của mình — một số trong số đó là lỗi của tôi. Trong bối cảnh này, tôi cần đưa ra một chút tuyên bố từ chối trách nhiệm: Hoạt động kinh doanh hàng ngày của tôi chịu ảnh hưởng rất nhiều bởi việc tôi sử dụng khung Jest để kiểm thử đơn vị và khung Cypress để kiểm thử đầu cuối. Tôi sẽ cố gắng hết sức để giữ cho phân tích của mình trừu tượng, để bạn cũng có thể sử dụng lời khuyên với các khung khác. Nếu bạn thấy điều đó không khả thi, vui lòng bình luận bên dưới để chúng ta có thể thảo luận về vấn đề này! Một số ví dụ thậm chí có thể áp dụng cho tất cả các loại kiểm thử, cho dù là kiểm thử đơn vị, tích hợp hay đầu cuối.
Tuy nhiên, giá trị này có thể bị lu mờ vì nhiều điểm khó khăn có nhiều nguyên nhân khác nhau. Nhiều trong số này có thể được coi là "bẫy". Hãy tưởng tượng bạn làm điều gì đó với ý định tốt nhất, nhưng cuối cùng lại trở nên đau đớn và kiệt sức: Đây là loại nợ kỹ thuật tồi tệ nhất.
Trong hướng dẫn “Thực hành tốt nhất về thử nghiệm JavaScript,” Yoni Goldberg nêu rõ quy tắc vàng để ngăn chặn các thử nghiệm trở thành gánh nặng: Một thử nghiệm phải giống như một trợ lý thân thiện, luôn ở đó để giúp bạn và không bao giờ được cảm thấy như một trở ngại.
Tôi đồng ý. Đây là điều quan trọng nhất trong thử nghiệm. Nhưng chính xác thì chúng ta đạt được điều này như thế nào? Cảnh báo tiết lộ nội dung: Hầu hết các ví dụ của tôi sẽ minh họa cho điều này. Nguyên tắc KISS (giữ cho đơn giản, ngốc ạ) là chìa khóa. Bất kỳ bài kiểm tra nào, bất kể loại nào, đều phải được thiết kế đơn giản và rõ ràng.
Vậy, bài kiểm tra đơn giản và rõ ràng là gì? Làm sao bạn biết được bài kiểm tra của mình đủ đơn giản? Không làm phức tạp các bài kiểm tra của bạn là điều tối quan trọng. Mục tiêu chính đã được Yoni Goldberg tóm tắt một cách hoàn hảo:
Nguyên tắc kiểm tra yêu thích của tôi liên quan đến sự trùng lặp, nguyên tắc DRY: Đừng lặp lại chính mình. Nếu tính trừu tượng cản trở khả năng hiểu được bài kiểm tra của bạn, thì hãy tránh hoàn toàn mã trùng lặp.
Đoạn mã này là một ví dụ:
Để làm cho bài kiểm tra dễ hiểu hơn, bạn có thể nghĩ rằng việc đặt tên lệnh có ý nghĩa là không đủ. Thay vào đó, bạn cũng có thể cân nhắc ghi lại các lệnh trong phần bình luận, như sau:
Tài liệu như vậy có thể cần thiết trong trường hợp này vì nó sẽ giúp bản thân tương lai và nhóm của bạn hiểu rõ hơn về bài kiểm tra. Bạn thấy đấy, một số phương pháp hay nhất cho mã sản xuất không phù hợp với mã kiểm tra. Các bài kiểm tra đơn giản không phải là mã sản xuất và chúng ta không bao giờ nên coi chúng như vậy. Tất nhiên, chúng ta nên xử lý mã kiểm tra cẩn thận như đối với mã sản xuất. Tuy nhiên, một số quy ước và phương pháp hay nhất có thể xung đột với khả năng hiểu được. Trong những trường hợp như vậy, hãy nhớ quy tắc vàng và đặt trải nghiệm của nhà phát triển lên hàng đầu.
Khi nhìn vào bài kiểm tra này, bạn có thể biết ngay mục đích của nó là gì không? Đặc biệt, hãy tưởng tượng bạn đang nhìn vào tiêu đề này trong kết quả kiểm tra của mình (ví dụ, bạn có thể đang xem các mục nhật ký trong đường ống của mình trong tích hợp liên tục). Vâng, rõ ràng là nó sẽ ném ra lỗi. Nhưng đó là lỗi gì? Trong trường hợp nào thì nó nên được ném ra? Bạn thấy đấy, hiểu ngay mục đích của bài kiểm tra này là gì không phải là điều dễ dàng vì tiêu đề không có nhiều ý nghĩa.
Hãy nhớ nguyên tắc vàng của chúng tôi, rằng chúng ta phải biết ngay mục đích của bài kiểm tra. Vì vậy, chúng ta cần thay đổi phần này. May mắn thay, có một giải pháp dễ hiểu. Chúng ta sẽ đặt tiêu đề cho bài kiểm tra này theo quy tắc ba.
Quy tắc này, do Roy Osherove giới thiệu, sẽ giúp bạn làm rõ mục đích của một bài kiểm tra. Đây là một thông lệ nổi tiếng trong thử nghiệm đơn vị, nhưng cũng hữu ích trong thử nghiệm đầu cuối. Theo quy tắc, tiêu đề của một bài kiểm tra phải bao gồm ba phần:
Đúng vậy, tiêu đề dài, nhưng bạn sẽ tìm thấy cả ba phần trong đó:
Nếu bạn hiểu, thì xin chúc mừng! Bạn xử lý thông tin cực kỳ nhanh. Nếu không, thì đừng lo; điều này khá bình thường, vì cấu trúc của bài kiểm tra có thể được cải thiện đáng kể. Ví dụ, các khai báo và khẳng định được viết và trộn lẫn mà không chú ý đến cấu trúc. Làm thế nào chúng ta có thể cải thiện bài kiểm tra này?
Có một mẫu có thể hữu ích, đó là mẫu AAA. AAA là viết tắt của “arrange, act, assert”, cho bạn biết phải làm gì để cấu trúc bài kiểm tra một cách rõ ràng. Chia bài kiểm tra thành ba phần quan trọng. Phù hợp với các bài kiểm tra tương đối ngắn, mẫu này chủ yếu được gặp trong thử nghiệm đơn vị. Tóm lại, đây là ba phần:
Nhưng hãy đợi đã! Phần này về hành động trước khi khẳng định là gì? Và khi chúng ta đang nói về nó, bạn không nghĩ rằng bài kiểm tra này có quá nhiều ngữ cảnh, vì là một bài kiểm tra đơn vị sao? Đúng vậy. Chúng ta đang xử lý các bài kiểm tra tích hợp ở đây. Nếu chúng ta đang kiểm tra DOM, như chúng ta đang làm ở đây, chúng ta sẽ cần kiểm tra các trạng thái trước và sau. Do đó, trong khi mô hình AAA phù hợp với các bài kiểm tra đơn vị và API, thì nó không phù hợp với trường hợp này.
Hãy xem xét mô hình AAA theo góc nhìn sau. Như Claudio Lassala đã nêu trong một trong những bài đăng trên blog của mình, thay vì nghĩ về cách tôi sẽ…
Tuy nhiên, bạn có thể sử dụng nó trong tất cả các loại bài kiểm tra — ví dụ, bằng cách cấu trúc các khối. Sử dụng ý tưởng từ các điểm bullet ở trên, việc viết lại bài kiểm tra ví dụ của chúng ta khá dễ dàng:

Tuy nhiên, họ có thể sẽ phải thức tỉnh một cách thô lỗ. Áp dụng hình ảnh này vào một bài kiểm tra, với hai người đại diện cho các bài kiểm tra và bài báo đại diện cho dữ liệu kiểm tra. Chúng ta hãy đặt tên cho hai bài kiểm tra này là bài kiểm tra A và bài kiểm tra B. Rất sáng tạo, phải không? Vấn đề là bài kiểm tra A và bài kiểm tra B chia sẻ cùng một dữ liệu kiểm tra hoặc tệ hơn là dựa vào một bài kiểm tra trước đó.
Điều này gây ra vấn đề vì nó dẫn đến các bài kiểm tra không ổn định. Ví dụ, nếu bài kiểm tra trước đó không thành công hoặc nếu dữ liệu kiểm tra được chia sẻ bị hỏng, thì bản thân các bài kiểm tra không thể chạy thành công. Một kịch bản khác là các bài kiểm tra của bạn được thực hiện theo thứ tự ngẫu nhiên. Khi điều này xảy ra, bạn không thể dự đoán liệu bài kiểm tra trước đó sẽ giữ nguyên thứ tự đó hay sẽ được hoàn thành sau các bài kiểm tra khác, trong trường hợp đó, các bài kiểm tra A và B sẽ mất cơ sở của chúng. Điều này cũng không giới hạn ở các bài kiểm tra đầu cuối; một trường hợp điển hình trong kiểm tra đơn vị là hai bài kiểm tra làm biến đổi cùng một dữ liệu hạt giống.
Được rồi, chúng ta hãy xem một ví dụ mã từ một bài kiểm tra đầu cuối trong công việc hàng ngày của tôi. Bài kiểm tra sau đây bao gồm chức năng đăng nhập của một cửa hàng trực tuyến.
Để tránh các vấn đề được đề cập ở trên, chúng ta sẽ thực hiện hook
Bước thứ hai là tạo tất cả dữ liệu cần thiết để chạy bài kiểm tra. Trong ví dụ của chúng ta, chúng ta cần tạo một khách hàng có thể đăng nhập vào cửa hàng của chúng ta. Tôi muốn tạo tất cả dữ liệu mà bài kiểm tra cần, được thiết kế riêng cho chính bài kiểm tra. Theo cách này, bài kiểm tra sẽ độc lập và thứ tự thực hiện có thể là ngẫu nhiên. Tóm lại, cả hai bước đều cần thiết để đảm bảo rằng các bài kiểm tra được cô lập khỏi mọi bài kiểm tra hoặc tác dụng phụ khác, do đó duy trì được tính ổn định.

Anh ấy đã tìm ra một cái tên có thể quen thuộc với chúng ta nhưng không quen thuộc với nó: Foo Bar. Tất nhiên, chúng tôi, những nhà phát triển, biết rằng Foo Bar thường được sử dụng làm tên tạm thời. Nhưng nếu bạn nhìn thấy nó trong một bài kiểm tra, bạn có biết ngay nó đại diện cho điều gì không? Một lần nữa, bài kiểm tra có thể khó hiểu hơn khi mới nhìn thấy.
May mắn thay, cái bẫy này rất dễ sửa. Hãy cùng xem bài kiểm tra Cypress bên dưới. Đây là một bài kiểm tra đầu cuối, nhưng lời khuyên không chỉ giới hạn ở loại này.
Bài kiểm tra này được cho là để kiểm tra xem sản phẩm có thể được tạo và đọc hay không. Trong bài kiểm tra này, tôi chỉ muốn sử dụng tên và chỗ giữ chỗ được kết nối với một sản phẩm thực:
Bạn có để ý đến các bộ chọn đó không? Chúng là bộ chọn CSS. Vâng, bạn có thể tự hỏi, "Tại sao chúng lại có vấn đề? Chúng độc đáo, dễ xử lý và bảo trì, và tôi có thể sử dụng chúng một cách hoàn hảo!" Tuy nhiên, bạn có chắc là điều đó luôn đúng không?
Sự thật là các bộ chọn CSS dễ thay đổi. Nếu bạn tái cấu trúc và, ví dụ, thay đổi các lớp, thì bài kiểm tra có thể không thành công, ngay cả khi bạn không đưa ra lỗi. Việc tái cấu trúc như vậy là phổ biến, vì vậy những lỗi đó có thể gây khó chịu và mệt mỏi cho các nhà phát triển để sửa. Vì vậy, hãy nhớ rằng một bài kiểm tra không đạt mà không có lỗi là kết quả dương tính giả, không đưa ra báo cáo đáng tin cậy nào cho ứng dụng của bạn.

Bẫy này chủ yếu đề cập đến thử nghiệm đầu cuối trong trường hợp này. Trong các trường hợp khác, nó cũng có thể áp dụng cho thử nghiệm đơn vị — ví dụ, nếu bạn sử dụng bộ chọn trong thử nghiệm thành phần. Như Kent C. Dodds đã nêu trong bài viết của anh ấy về chủ đề này:
Kết quả dương tính giả chỉ là một trong những rắc rối mà chúng ta gặp phải khi kiểm tra chi tiết triển khai. Ngược lại, kết quả âm tính giả cũng có thể xảy ra khi kiểm tra chi tiết triển khai. Kết quả dương tính giả xảy ra khi một bài kiểm tra vượt qua ngay cả khi ứng dụng có lỗi. Kết quả là việc kiểm tra lại chiếm hết dung lượng lưu trữ, trái ngược với quy tắc vàng của chúng ta. Vì vậy, chúng ta cần tránh điều này càng nhiều càng tốt.
Lưu ý: Chủ đề này rất rộng, vì vậy sẽ tốt hơn nếu được giải quyết trong một bài viết khác. Cho đến lúc đó, tôi đề xuất bạn nên đọc bài viết của Dodds về “Chi tiết triển khai thử nghiệm” để tìm hiểu thêm về chủ đề này.
Đó là vấn đề thời gian chờ cố định mà tôi đã nói đến trong bài viết của mình về các bài kiểm tra không ổn định. Hãy xem thử nghiệm này:
Dòng nhỏ có
Mọi con đường đều dẫn đến việc chờ động. Tôi đề xuất nên ưu tiên các phương pháp xác định hơn mà hầu hết các nền tảng kiểm tra cung cấp. Chúng ta hãy xem xét kỹ hơn hai phương pháp yêu thích của tôi.
Áp dụng điều đó vào thử nghiệm. Có thể phải mất rất nhiều nỗ lực để tránh rơi vào bẫy thử nghiệm hoặc khắc phục sự cố nếu thiệt hại đã xảy ra, đặc biệt là với mã cũ. Rất thường xuyên, bạn và nhóm của mình sẽ cần thay đổi tư duy khi thiết kế thử nghiệm hoặc thậm chí là phải tái cấu trúc rất nhiều. Nhưng cuối cùng, điều đó sẽ xứng đáng và cuối cùng bạn sẽ thấy được phần thưởng.
Điều quan trọng nhất cần nhớ là quy tắc vàng mà chúng ta đã nói đến trước đó. Hầu hết các ví dụ của tôi đều tuân theo quy tắc đó. Mọi điểm khó khăn đều phát sinh do bỏ qua quy tắc đó. Bài kiểm tra phải là trợ lý thân thiện, không phải là trở ngại! Đây là điều quan trọng nhất cần ghi nhớ. Bài kiểm tra phải giống như bạn đang thực hiện một thói quen, không phải giải một công thức toán học phức tạp. Hãy cố gắng hết sức để đạt được điều đó.

Tôi hy vọng mình có thể giúp bạn bằng cách đưa ra một số ý tưởng về những cạm bẫy phổ biến nhất mà tôi đã gặp phải. Tuy nhiên, tôi chắc chắn sẽ có nhiều cạm bẫy hơn để tìm và học hỏi. Tôi sẽ rất vui nếu bạn chia sẻ những cạm bẫy mà bạn đã gặp phải nhiều nhất trong phần bình luận bên dưới, để tất cả chúng ta cũng có thể học hỏi từ bạn. Hẹn gặp lại bạn ở đó!

“Đó là một cái bẫy!” Dòng này cảnh báo chúng ta về một cuộc phục kích bất ngờ, một mối nguy hiểm sắp xảy ra. Được rồi, nhưng điều này liên quan gì đến việc thử nghiệm? Vâng, nó chỉ đơn giản là một ẩn dụ thích hợp khi nói đến việc xử lý các bài kiểm tra trong cơ sở mã. Những cạm bẫy này có thể giống như một cuộc phục kích bất ngờ khi bạn làm việc trên cơ sở mã, đặc biệt là khi làm việc trong thời gian dài.
Trong bài viết này, tôi sẽ cho bạn biết những cạm bẫy mà tôi đã gặp phải trong sự nghiệp của mình — một số trong số đó là lỗi của tôi. Trong bối cảnh này, tôi cần đưa ra một chút tuyên bố từ chối trách nhiệm: Hoạt động kinh doanh hàng ngày của tôi chịu ảnh hưởng rất nhiều bởi việc tôi sử dụng khung Jest để kiểm thử đơn vị và khung Cypress để kiểm thử đầu cuối. Tôi sẽ cố gắng hết sức để giữ cho phân tích của mình trừu tượng, để bạn cũng có thể sử dụng lời khuyên với các khung khác. Nếu bạn thấy điều đó không khả thi, vui lòng bình luận bên dưới để chúng ta có thể thảo luận về vấn đề này! Một số ví dụ thậm chí có thể áp dụng cho tất cả các loại kiểm thử, cho dù là kiểm thử đơn vị, tích hợp hay đầu cuối.
Cạm bẫy kiểm thử đầu cuối
Kiểm thử, bất kể loại nào, đều có rất nhiều lợi ích. Kiểm thử đầu cuối là một tập hợp các thực hành để kiểm thử giao diện người dùng của ứng dụng web. Chúng tôi kiểm tra chức năng của nó bằng cách đặt UI của nó dưới áp lực liên tục. Tùy thuộc vào loại thử nghiệm, chúng tôi có thể đạt được điều này theo nhiều cách khác nhau và ở nhiều cấp độ khác nhau:- Kiểm tra đơn vị xem xét các đơn vị nhỏ trong ứng dụng của bạn. Các đơn vị này có thể là lớp, giao diện hoặc phương thức. Các thử nghiệm kiểm tra xem chúng có đưa ra đầu ra mong đợi hay không, bằng cách sử dụng các đầu vào được xác định trước — do đó, kiểm tra các đơn vị riêng biệt và biệt lập.
- Kiểm tra tích hợp có phạm vi rộng hơn. Chúng kiểm tra các đơn vị mã cùng nhau, xem xét tương tác của chúng.
- Kiểm tra đầu cuối kiểm tra ứng dụng, như một người dùng thực tế sẽ làm. Do đó, nó giống với kiểm tra hệ thống nếu chúng ta xem xét đảm bảo chất lượng về mặt lý thuyết.
Tuy nhiên, giá trị này có thể bị lu mờ vì nhiều điểm khó khăn có nhiều nguyên nhân khác nhau. Nhiều trong số này có thể được coi là "bẫy". Hãy tưởng tượng bạn làm điều gì đó với ý định tốt nhất, nhưng cuối cùng lại trở nên đau đớn và kiệt sức: Đây là loại nợ kỹ thuật tồi tệ nhất.
Tại sao chúng ta nên bận tâm đến bẫy thử nghiệm?
Khi tôi nghĩ về nguyên nhân và hậu quả của các bẫy thử nghiệm front-end mà tôi đã mắc phải, một số vấn đề nhất định hiện ra trong đầu tôi. Có ba nguyên nhân đặc biệt liên tục xuất hiện trong đầu tôi, phát sinh từ mã cũ mà tôi đã viết cách đây nhiều năm.- Kiểm tra chậm hoặc ít nhất là thực hiện kiểm tra chậm.
Khi phát triển cục bộ, các nhà phát triển có xu hướng mất kiên nhẫn với các bài kiểm tra, đặc biệt là nếu ai đó trong nhóm của bạn cần hợp nhất các yêu cầu kéo tương ứng. Thời gian chờ đợi lâu luôn gây khó chịu vô cùng trong mọi trường hợp. Cái bẫy này có thể phát sinh từ nhiều nguyên nhân nhỏ — ví dụ, không chú ý nhiều đến thời gian chờ đợi phù hợp hoặc phạm vi của một bài kiểm tra. - Các bài kiểm tra khó bảo trì.
Điểm khó khăn thứ hai này thậm chí còn nghiêm trọng hơn và là nguyên nhân đáng kể hơn khiến các bài kiểm tra bị bỏ dở. Ví dụ, bạn có thể quay lại một bài kiểm tra sau nhiều tháng và không hiểu nội dung hoặc mục đích của nó chút nào. Hoặc các thành viên trong nhóm có thể hỏi bạn muốn đạt được điều gì với một bài kiểm tra cũ mà bạn đã viết. Nhìn chung, quá nhiều lớp hoặc trừu tượng nằm rải rác trên các bức tường văn bản hoặc mã có thể nhanh chóng giết chết động lực của một nhà phát triển và dẫn đến sự hỗn loạn thực sự. Bẫy trong khu vực này có thể xảy ra do làm theo các biện pháp thực hành tốt nhất không phù hợp với các bài kiểm tra. - Các bài kiểm tra không cung cấp cho bạn giá trị nhất quán nào cả.
Bạn có thể gọi chúng là Heisenfails hoặc Heisentests, giống như Heisenbug nổi tiếng, chỉ xảy ra nếu bạn nhìn đi chỗ khác, không đo lường hoặc, trong trường hợp của chúng tôi, không gỡ lỗi. Trường hợp tệ nhất là một bài kiểm tra không ổn định, một bài kiểm tra không xác định không cung cấp cùng một kết quả giữa các bản dựng mà không có bất kỳ thay đổi nào. Điều này có thể xảy ra vì nhiều lý do, nhưng thường xảy ra khi bạn cố gắng thực hiện một lối tắt dễ dàng, có vẻ tiện lợi, bỏ qua các biện pháp thực hành tốt nhất về thử nghiệm.
Quy tắc vàng
Giả sử bạn đang làm một công việc thú vị nhưng đòi hỏi cao. Bạn tập trung hoàn toàn vào công việc đó. Não bạn chứa đầy mã sản xuất, không còn không gian cho bất kỳ sự phức tạp nào nữa — đặc biệt là để thử nghiệm. Chiếm nhiều không gian hoàn toàn trái ngược với mục đích của thử nghiệm. Trong trường hợp tệ nhất, các thử nghiệm giống như gánh nặng là lý do khiến nhiều nhóm từ bỏ chúng.Trong hướng dẫn “Thực hành tốt nhất về thử nghiệm JavaScript,” Yoni Goldberg nêu rõ quy tắc vàng để ngăn chặn các thử nghiệm trở thành gánh nặng: Một thử nghiệm phải giống như một trợ lý thân thiện, luôn ở đó để giúp bạn và không bao giờ được cảm thấy như một trở ngại.
Tôi đồng ý. Đây là điều quan trọng nhất trong thử nghiệm. Nhưng chính xác thì chúng ta đạt được điều này như thế nào? Cảnh báo tiết lộ nội dung: Hầu hết các ví dụ của tôi sẽ minh họa cho điều này. Nguyên tắc KISS (giữ cho đơn giản, ngốc ạ) là chìa khóa. Bất kỳ bài kiểm tra nào, bất kể loại nào, đều phải được thiết kế đơn giản và rõ ràng.
Vậy, bài kiểm tra đơn giản và rõ ràng là gì? Làm sao bạn biết được bài kiểm tra của mình đủ đơn giản? Không làm phức tạp các bài kiểm tra của bạn là điều tối quan trọng. Mục tiêu chính đã được Yoni Goldberg tóm tắt một cách hoàn hảo:
Vì vậy, thiết kế của một bài kiểm tra phải phẳng. Tối giản là cách diễn đạt tốt nhất. Một bài kiểm tra không nên có nhiều logic và ít hoặc không có sự trừu tượng nào cả. Điều này cũng có nghĩa là bạn cần phải thận trọng với các đối tượng và lệnh trang, và bạn cần đặt tên và ghi lại các lệnh một cách có ý nghĩa. Nếu bạn định sử dụng chúng, hãy chú ý đến các lệnh chỉ định, hàm và tên lớp. Theo cách này, một bài kiểm tra sẽ vẫn thú vị đối với cả nhà phát triển và người kiểm tra.“Người ta nên xem một bài kiểm tra và nắm bắt ý định ngay lập tức.”
Nguyên tắc kiểm tra yêu thích của tôi liên quan đến sự trùng lặp, nguyên tắc DRY: Đừng lặp lại chính mình. Nếu tính trừu tượng cản trở khả năng hiểu được bài kiểm tra của bạn, thì hãy tránh hoàn toàn mã trùng lặp.
Đoạn mã này là một ví dụ:
Mã:
// CypressbeforeEach(() => { // Thoạt nhìn, thật khó để thấy những // lệnh đó thực sự làm gì cy.setInitialState() .then(() => { return cy.login(); })}):
Mã:
// Cypress/*** Đăng nhập âm thầm bằng API* @memberOf Cypress.Chainable#* @name loginViaApi* @function*/Cypress.Commands.add('loginViaApi', () => { return cy.authenticate().then((result) => { return cy.window().then(() => { cy.setCookie('bearerAuth', result); }).then(() => { cy.log('Fixtures are created.'); }); });});
Bẫy trong thiết kế kiểm tra
Trong một vài ví dụ đầu tiên trong phần này, tôi sẽ nói về cách tránh rơi vào bẫy kiểm tra ngay từ đầu. Sau đó, tôi sẽ nói về thiết kế kiểm tra. Nếu bạn đã làm việc trên một dự án lâu dài, điều này vẫn có thể hữu ích.Quy tắc ba
Chúng ta hãy bắt đầu với ví dụ bên dưới. Hãy chú ý đến tiêu đề của ví dụ. Bản thân nội dung của bài kiểm tra chỉ là thứ yếu.
Mã:
// Jestdescribe('deprecated.plugin', () => { it('should throw error',() => { // Bài kiểm tra thực tế, viết tắt của component throw // lỗi const component = createComponent(); expect(global.console.error).toBeCalled(); });});
Hãy nhớ nguyên tắc vàng của chúng tôi, rằng chúng ta phải biết ngay mục đích của bài kiểm tra. Vì vậy, chúng ta cần thay đổi phần này. May mắn thay, có một giải pháp dễ hiểu. Chúng ta sẽ đặt tiêu đề cho bài kiểm tra này theo quy tắc ba.
Quy tắc này, do Roy Osherove giới thiệu, sẽ giúp bạn làm rõ mục đích của một bài kiểm tra. Đây là một thông lệ nổi tiếng trong thử nghiệm đơn vị, nhưng cũng hữu ích trong thử nghiệm đầu cuối. Theo quy tắc, tiêu đề của một bài kiểm tra phải bao gồm ba phần:
- Cái gì đang được kiểm tra?
- Nó sẽ được kiểm tra trong những trường hợp nào?
- Kết quả mong đợi là gì?
Mã:
// Jestdescribe('deprecated.plugin', () => {it('Property: Should throw an error if the deprecated prop is used', () => { // Kiểm tra thực tế, rút gọn cho component throw // lỗi const component = createComponent(); expect(global.console.error).toBeCalled(); });});
- Cái gì đang được kiểm tra? Trong trường hợp này, đó là thuộc tính.
- Trong trường hợp nào? Chúng ta muốn kiểm tra một thuộc tính đã lỗi thời.
- Chúng ta mong đợi điều gì? Ứng dụng sẽ throw an error.
“Sắp xếp, Hành động, Khẳng định” so với “Được đưa ra, Khi nào, Sau đó”
Một cái bẫy khác, một ví dụ mã khác. Bạn có hiểu bài kiểm tra sau đây khi đọc lần đầu không?
Mã:
// Jestdescribe('Context menu', () => { it('should open the context menu on click', async () => { const contextButtonSelector = 'sw-context-button'; const contextButton = wrapper.find(contextButtonSelector); await contextButton.trigger('click'); const contextMenuSelector = '.sw-context-menu'; let contextMenu = wrapper.find(contextMenuSelector); expect(contextMenu.isVisible()).toBe(false); contextMenu = wrapper.find(contextMenuSelector); expect(contextMenu.isVisible()).toBe(true); });});
Có một mẫu có thể hữu ích, đó là mẫu AAA. AAA là viết tắt của “arrange, act, assert”, cho bạn biết phải làm gì để cấu trúc bài kiểm tra một cách rõ ràng. Chia bài kiểm tra thành ba phần quan trọng. Phù hợp với các bài kiểm tra tương đối ngắn, mẫu này chủ yếu được gặp trong thử nghiệm đơn vị. Tóm lại, đây là ba phần:
- Sắp xếp
Tại đây, bạn sẽ thiết lập hệ thống đang được thử nghiệm để đạt được kịch bản mà thử nghiệm hướng đến mô phỏng. Điều này có thể bao gồm bất kỳ điều gì từ thiết lập biến đến làm việc với bản nháp và bản nháp. - Hành động
Trong phần này, bạn sẽ chạy đơn vị trong thử nghiệm. Vì vậy, bạn sẽ thực hiện tất cả các bước và bất kỳ điều gì cần thực hiện để đạt được trạng thái kết quả của thử nghiệm. - Khẳng định
Phần này tương đối dễ hiểu. Bạn chỉ cần đưa ra các khẳng định và kiểm tra của mình trong phần cuối này.
Mã:
// Jestdescribe('Context menu', () => { it('should open the context menu on click', () => { // Arrange const contextButtonSelector = 'sw-context-button'; const contextMenuSelector = '.sw-context-menu'; // Xác nhận trạng thái trước khi kiểm tra let contextMenu = wrapper.find(contextMenuSelector); expect(contextMenu.isVisible()).toBe(false); // Act const contextButton = wrapper.find(contextButtonSelector); await contextButton.trigger('click'); // Xác nhận contextMenu = wrapper.find(contextMenuSelector); expect(contextMenu.isVisible()).toBe(true); });});
Hãy xem xét mô hình AAA theo góc nhìn sau. Như Claudio Lassala đã nêu trong một trong những bài đăng trên blog của mình, thay vì nghĩ về cách tôi sẽ…
- “…sắp xếp bài kiểm tra của mình, tôi nghĩ những gì tôi được cho.”
Đây là kịch bản với tất cả các điều kiện tiên quyết của bài kiểm tra. - “…hành động trong bài kiểm tra của mình, tôi nghĩ khi có điều gì đó xảy ra.”
Ở đây, chúng ta thấy các hành động của bài kiểm tra. - “…khẳng định kết quả, tôi nghĩ nếu điều đó xảy ra thì đây là điều tôi mong đợi là kết quả.”
Ở đây, chúng ta tìm thấy những điều chúng ta muốn khẳng định, đó là mục đích của bài kiểm tra.
Mã:
Tính năng: Menu ngữ cảnh Kịch bản: Giả sử tôi có một bộ chọn cho menu ngữ cảnh Và tôi có một bộ chọn cho nút ngữ cảnh Khi có thể tìm thấy menu ngữ cảnh Và menu này hiển thị Và nút ngữ cảnh này có thể tìm thấy Và được nhấp vào Sau đó, tôi sẽ có thể tìm thấy contextMenu trong DOM Và menu ngữ cảnh này hiển thị
Mã:
// Jestdescribe('Context menu', () => { it('should open the context menu on click', () => { // Cho const contextButtonSelector = 'sw-context-button'; const contextMenuSelector = '.sw-context-menu'; // Khi let contextMenu = wrapper.find(contextMenuSelector); expect(contextMenu.isVisible()).toBe(false); const contextButton = wrapper.find(contextButtonSelector); await contextButton.trigger('click'); // Then contextMenu = wrapper.find(contextMenuSelector); expect(contextMenu.isVisible()).toBe(true); });});
Dữ liệu chúng tôi từng chia sẻ
Chúng ta đã đến cái bẫy tiếp theo. Hình ảnh bên dưới trông yên bình và hạnh phúc, hai người chia sẻ một tờ giấy:
Tuy nhiên, họ có thể sẽ phải thức tỉnh một cách thô lỗ. Áp dụng hình ảnh này vào một bài kiểm tra, với hai người đại diện cho các bài kiểm tra và bài báo đại diện cho dữ liệu kiểm tra. Chúng ta hãy đặt tên cho hai bài kiểm tra này là bài kiểm tra A và bài kiểm tra B. Rất sáng tạo, phải không? Vấn đề là bài kiểm tra A và bài kiểm tra B chia sẻ cùng một dữ liệu kiểm tra hoặc tệ hơn là dựa vào một bài kiểm tra trước đó.
Điều này gây ra vấn đề vì nó dẫn đến các bài kiểm tra không ổn định. Ví dụ, nếu bài kiểm tra trước đó không thành công hoặc nếu dữ liệu kiểm tra được chia sẻ bị hỏng, thì bản thân các bài kiểm tra không thể chạy thành công. Một kịch bản khác là các bài kiểm tra của bạn được thực hiện theo thứ tự ngẫu nhiên. Khi điều này xảy ra, bạn không thể dự đoán liệu bài kiểm tra trước đó sẽ giữ nguyên thứ tự đó hay sẽ được hoàn thành sau các bài kiểm tra khác, trong trường hợp đó, các bài kiểm tra A và B sẽ mất cơ sở của chúng. Điều này cũng không giới hạn ở các bài kiểm tra đầu cuối; một trường hợp điển hình trong kiểm tra đơn vị là hai bài kiểm tra làm biến đổi cùng một dữ liệu hạt giống.
Được rồi, chúng ta hãy xem một ví dụ mã từ một bài kiểm tra đầu cuối trong công việc hàng ngày của tôi. Bài kiểm tra sau đây bao gồm chức năng đăng nhập của một cửa hàng trực tuyến.
Mã:
// Cypressdescribe('Customer login', () => { // Thực hiện trước mỗi bài kiểm tra beforeEach(() => { // Bước 1: Đặt ứng dụng ở trạng thái sạch cy.setInitialState() .then(() => { // Bước 2: Tạo dữ liệu kiểm tra return cy.setFixture('customer'); }) // … sử dụng cy.request để tạo khách hàng }): // … các bài kiểm tra sẽ bắt đầu bên dưới})
beforeEach
của bài kiểm tra này trước mỗi bài kiểm tra trong tệp của nó. Trong đó, bước đầu tiên và quan trọng nhất mà chúng ta sẽ thực hiện là đặt lại ứng dụng về cài đặt gốc, không có bất kỳ dữ liệu tùy chỉnh nào hoặc bất kỳ thứ gì. Mục tiêu của chúng ta ở đây là đảm bảo rằng tất cả các bài kiểm tra của chúng ta đều có cùng một cơ sở. Ngoài ra, nó bảo vệ bài kiểm tra này khỏi mọi tác dụng phụ bên ngoài bài kiểm tra. Về cơ bản, chúng ta đang cô lập nó, tránh xa mọi ảnh hưởng từ bên ngoài.Bước thứ hai là tạo tất cả dữ liệu cần thiết để chạy bài kiểm tra. Trong ví dụ của chúng ta, chúng ta cần tạo một khách hàng có thể đăng nhập vào cửa hàng của chúng ta. Tôi muốn tạo tất cả dữ liệu mà bài kiểm tra cần, được thiết kế riêng cho chính bài kiểm tra. Theo cách này, bài kiểm tra sẽ độc lập và thứ tự thực hiện có thể là ngẫu nhiên. Tóm lại, cả hai bước đều cần thiết để đảm bảo rằng các bài kiểm tra được cô lập khỏi mọi bài kiểm tra hoặc tác dụng phụ khác, do đó duy trì được tính ổn định.
Bẫy triển khai
Được rồi, chúng ta đã nói về thiết kế bài kiểm tra. Tuy nhiên, nói về thiết kế bài kiểm tra tốt là chưa đủ, vì vấn đề nằm ở chi tiết. Vậy hãy cùng kiểm tra các bài kiểm tra của chúng ta và thách thức việc triển khai thực tế của bài kiểm tra.Foo Bar What?
Đối với cái bẫy đầu tiên trong việc triển khai bài kiểm tra này, chúng ta có một vị khách! Đó là BB-8 và anh ấy đã tìm thấy thứ gì đó trong một trong các bài kiểm tra của chúng ta:
Anh ấy đã tìm ra một cái tên có thể quen thuộc với chúng ta nhưng không quen thuộc với nó: Foo Bar. Tất nhiên, chúng tôi, những nhà phát triển, biết rằng Foo Bar thường được sử dụng làm tên tạm thời. Nhưng nếu bạn nhìn thấy nó trong một bài kiểm tra, bạn có biết ngay nó đại diện cho điều gì không? Một lần nữa, bài kiểm tra có thể khó hiểu hơn khi mới nhìn thấy.
May mắn thay, cái bẫy này rất dễ sửa. Hãy cùng xem bài kiểm tra Cypress bên dưới. Đây là một bài kiểm tra đầu cuối, nhưng lời khuyên không chỉ giới hạn ở loại này.
Mã:
// Cypressit('should create and read product', () => { // Mở mô-đun để thêm sản phẩm cy.get('a[href="#/sw/product/create"]').click(); // Thêm dữ liệu cơ bản vào sản phẩm cy.get('.sw-field—product-name').type('T-Shirt Ackbar'); cy.get('.sw-select-product__select_manufacturer') .type('Space Company'); // … bài kiểm tra tiếp tục …});
- Đối với tên của một sản phẩm áo phông, tôi muốn sử dụng "T-Shirt Akbar".
- Đối với tên nhà sản xuất, "Space Company" là một ý tưởng.
Xem xét các bộ chọn, bạn phải
Bẫy mới, cùng một bài kiểm tra. Hãy xem lại lần nữa, bạn có nhận thấy điều gì không?
Mã:
// Cypressit('should create and read product', () => { // Mở mô-đun để thêm sản phẩm cy.get('a[href="#/sw/product/create"]').click(); // Thêm dữ liệu cơ bản vào sản phẩm cy.get('.sw-field—product-name').type('T-Shirt Ackbar'); cy.get('.sw-select-product__select_manufacturer') .type('Space Company'); // … Kiểm tra tiếp tục …});
Sự thật là các bộ chọn CSS dễ thay đổi. Nếu bạn tái cấu trúc và, ví dụ, thay đổi các lớp, thì bài kiểm tra có thể không thành công, ngay cả khi bạn không đưa ra lỗi. Việc tái cấu trúc như vậy là phổ biến, vì vậy những lỗi đó có thể gây khó chịu và mệt mỏi cho các nhà phát triển để sửa. Vì vậy, hãy nhớ rằng một bài kiểm tra không đạt mà không có lỗi là kết quả dương tính giả, không đưa ra báo cáo đáng tin cậy nào cho ứng dụng của bạn.

Bẫy này chủ yếu đề cập đến thử nghiệm đầu cuối trong trường hợp này. Trong các trường hợp khác, nó cũng có thể áp dụng cho thử nghiệm đơn vị — ví dụ, nếu bạn sử dụng bộ chọn trong thử nghiệm thành phần. Như Kent C. Dodds đã nêu trong bài viết của anh ấy về chủ đề này:
Theo tôi, có những giải pháp thay thế tốt hơn cho việc sử dụng các chi tiết triển khai để kiểm tra. Thay vào đó, hãy kiểm tra những thứ mà người dùng sẽ nhận thấy. Tốt hơn nữa, hãy chọn các bộ chọn ít có khả năng thay đổi hơn. Loại bộ chọn yêu thích của tôi là thuộc tính dữ liệu. Nhà phát triển ít có khả năng thay đổi các thuộc tính dữ liệu trong khi tái cấu trúc, khiến chúng trở nên hoàn hảo để định vị các thành phần trong các bài kiểm tra. Tôi khuyên bạn nên đặt tên cho chúng theo cách có ý nghĩa để truyền đạt rõ ràng mục đích của chúng cho bất kỳ nhà phát triển nào đang làm việc trên mã nguồn. Nó có thể trông như thế này:“Bạn không nên kiểm tra các chi tiết triển khai.”
Mã:
// Cypresscy.get('[data-test=sw-field—product-name]') .type('T-Shirt Ackbar');cy.get('[data-test=sw-select-product__select_manufacturer]') .type('Space Company');
Lưu ý: Chủ đề này rất rộng, vì vậy sẽ tốt hơn nếu được giải quyết trong một bài viết khác. Cho đến lúc đó, tôi đề xuất bạn nên đọc bài viết của Dodds về “Chi tiết triển khai thử nghiệm” để tìm hiểu thêm về chủ đề này.
Hãy chờ xem!
Cuối cùng nhưng không kém phần quan trọng, đây là chủ đề mà tôi không thể nhấn mạnh đủ. Tôi biết điều này sẽ gây khó chịu, nhưng tôi vẫn thấy nhiều người làm vậy, vì vậy tôi cần đề cập đến nó ở đây như một cái bẫy.Đó là vấn đề thời gian chờ cố định mà tôi đã nói đến trong bài viết của mình về các bài kiểm tra không ổn định. Hãy xem thử nghiệm này:
Mã:
// CypressCypress.Commands.add('typeSingleSelect', { prevSubject: 'element', }, (subject, value, selector) => { cy.wrap(subject).should('be.visible'); cy.wrap(subject).click(); cy.wait(500); cy.get(`${selector} input`) .type(value);});
cy.wait(500)
là thời gian chờ cố định tạm dừng thực thi thử nghiệm trong nửa giây. Làm cho lỗi này nghiêm trọng hơn, bạn sẽ tìm thấy nó trong một lệnh tùy chỉnh, do đó thử nghiệm sẽ sử dụng thời gian chờ này nhiều lần. Số giây sẽ tăng lên sau mỗi lần sử dụng lệnh này. Điều đó sẽ làm chậm thử nghiệm quá nhiều và điều đó hoàn toàn không cần thiết. Và đó thậm chí không phải là phần tệ nhất. Phần tệ nhất là chúng ta sẽ phải đợi quá ít thời gian, do đó, bài kiểm tra của chúng ta sẽ thực hiện nhanh hơn so với khả năng phản ứng của trang web. Điều này sẽ gây ra sự không ổn định, vì đôi khi bài kiểm tra sẽ không thành công. May mắn thay, chúng ta có thể làm nhiều việc để tránh thời gian chờ cố định.Mọi con đường đều dẫn đến việc chờ động. Tôi đề xuất nên ưu tiên các phương pháp xác định hơn mà hầu hết các nền tảng kiểm tra cung cấp. Chúng ta hãy xem xét kỹ hơn hai phương pháp yêu thích của tôi.
- Chờ thay đổi trong UI.
Phương pháp đầu tiên tôi lựa chọn là chờ thay đổi trong UI của ứng dụng mà người dùng sẽ nhận thấy hoặc thậm chí phản ứng. Ví dụ có thể bao gồm thay đổi trong UI (như vòng quay tải biến mất), chờ hoạt ảnh dừng lại, v.v. Nếu bạn sử dụng Cypress, điều này có thể trông như sau:
```Mã:// Cypress```cy.get('data-cy="submit"').should('be.visible');
- Chờ yêu cầu API.
Một khả năng khác mà tôi yêu thích là chờ yêu cầu API và phản hồi của chúng. Để nêu một ví dụ, Cypress cung cấp các tính năng gọn gàng cho việc đó. Đầu tiên, bạn sẽ định nghĩa một tuyến đường mà Cypress sẽ chờ:
Mã:// Cypresscy.intercept({ url: '/widgets/checkout/info', method: 'GET'}).as('checkoutAvailable');
Mã:// Cypresscy.wait('@request').its('response.statusCode') .should('equal', 200);
Những điểm chính
Quay lại với Đô đốc Akbar và Star Wars nói chung, Trận chiến Endor đã trở thành một thành công, mặc dù phải thực hiện rất nhiều công việc để đạt được chiến thắng đó. Với tinh thần đồng đội và một vài biện pháp đối phó, điều đó đã khả thi và cuối cùng đã trở thành hiện thực.Áp dụng điều đó vào thử nghiệm. Có thể phải mất rất nhiều nỗ lực để tránh rơi vào bẫy thử nghiệm hoặc khắc phục sự cố nếu thiệt hại đã xảy ra, đặc biệt là với mã cũ. Rất thường xuyên, bạn và nhóm của mình sẽ cần thay đổi tư duy khi thiết kế thử nghiệm hoặc thậm chí là phải tái cấu trúc rất nhiều. Nhưng cuối cùng, điều đó sẽ xứng đáng và cuối cùng bạn sẽ thấy được phần thưởng.
Điều quan trọng nhất cần nhớ là quy tắc vàng mà chúng ta đã nói đến trước đó. Hầu hết các ví dụ của tôi đều tuân theo quy tắc đó. Mọi điểm khó khăn đều phát sinh do bỏ qua quy tắc đó. Bài kiểm tra phải là trợ lý thân thiện, không phải là trở ngại! Đây là điều quan trọng nhất cần ghi nhớ. Bài kiểm tra phải giống như bạn đang thực hiện một thói quen, không phải giải một công thức toán học phức tạp. Hãy cố gắng hết sức để đạt được điều đó.

Tôi hy vọng mình có thể giúp bạn bằng cách đưa ra một số ý tưởng về những cạm bẫy phổ biến nhất mà tôi đã gặp phải. Tuy nhiên, tôi chắc chắn sẽ có nhiều cạm bẫy hơn để tìm và học hỏi. Tôi sẽ rất vui nếu bạn chia sẻ những cạm bẫy mà bạn đã gặp phải nhiều nhất trong phần bình luận bên dưới, để tất cả chúng ta cũng có thể học hỏi từ bạn. Hẹn gặp lại bạn ở đó!
Các nguồn tài nguyên khác
- “Các phương pháp hay nhất về kiểm thử JavaScript và Node.js,” Yoni Goldberg
- “Chi tiết triển khai kiểm thử,” Kent C. Dodds
- “Tiêu chuẩn đặt tên cho các bài kiểm tra đơn vị.html,” Roy Osherove
Đọc thêm
- Sự cường điệu xung quanh các tín hiệu
- Tiêu đề cố định và Các phần tử toàn chiều cao: Một sự kết hợp khó khăn
- Kim tự tháp thử nghiệm trường tồn
- Tạo biểu mẫu nhiều bước hiệu quả để có trải nghiệm người dùng tốt hơn