Làm thế nào để tránh các bài kiểm tra đơn vị dễ vỡ?


24

Chúng tôi đã viết gần 3.000 bài kiểm tra - dữ liệu đã được mã hóa cứng, rất ít sử dụng lại mã. Phương pháp này đã bắt đầu cắn chúng tôi vào mông. Khi hệ thống thay đổi, chúng tôi thấy mình dành nhiều thời gian hơn để sửa các bài kiểm tra bị hỏng. Chúng tôi có đơn vị, tích hợp và kiểm tra chức năng.

Những gì tôi đang tìm kiếm là một cách dứt khoát để viết các bài kiểm tra có thể quản lý và duy trì.

Khung


Điều này phù hợp hơn nhiều với Lập trình viên
.StackExchange

Câu trả lời:


21

Đừng nghĩ về chúng như là "các bài kiểm tra đơn vị bị hỏng", bởi vì chúng không phải là.

Chúng là thông số kỹ thuật, mà chương trình của bạn không còn hỗ trợ.

Đừng nghĩ đó là "sửa chữa các bài kiểm tra", mà là "xác định các yêu cầu mới".

Các bài kiểm tra nên xác định ứng dụng của bạn trước, không phải cách khác.

Bạn không thể nói rằng bạn có một triển khai làm việc cho đến khi bạn biết nó hoạt động. Bạn không thể nói nó hoạt động cho đến khi bạn kiểm tra nó.

Một vài lưu ý khác có thể hướng dẫn bạn:

  1. Các bài kiểm tra các lớp học kiểm tra nên ngắn gọn và đơn giản . Mỗi bài kiểm tra chỉ nên kiểm tra một phần chức năng gắn kết. Đó là, nó không quan tâm đến những thứ mà các bài kiểm tra khác đã kiểm tra.
  2. Các thử nghiệm và các đối tượng của bạn nên được ghép lỏng lẻo, theo cách mà nếu bạn thay đổi một đối tượng, bạn chỉ thay đổi biểu đồ phụ thuộc của nó xuống dưới và các đối tượng khác sử dụng đối tượng đó không bị ảnh hưởng bởi nó.
  3. Bạn có thể đang tạo và kiểm tra những thứ sai . Là các đối tượng của bạn được xây dựng để dễ dàng giao tiếp, hoặc thực hiện dễ dàng? Nếu đó là trường hợp sau, bạn sẽ thấy mình thay đổi rất nhiều mã sử dụng giao diện của triển khai cũ.
  4. Trong trường hợp tốt nhất, hãy tuân thủ nghiêm ngặt nguyên tắc Trách nhiệm duy nhất. Trong trường hợp xấu hơn, hãy tuân thủ nguyên tắc Phân chia Giao diện. Xem các nguyên tắc RẮN .

5
+1 choDon't think of it as "fixing the tests", but as "defining new requirements".
StuperUser

2
+1 Các bài kiểm tra nên chỉ định ứng dụng của bạn trước, không phải theo cách khác
giải mã tre

11

Những gì bạn mô tả thực sự có thể không phải là một điều tồi tệ, nhưng là một con trỏ cho các vấn đề sâu hơn mà các bài kiểm tra của bạn phát hiện ra

Khi hệ thống thay đổi, chúng tôi thấy mình dành nhiều thời gian hơn để sửa các bài kiểm tra bị hỏng. Chúng tôi có đơn vị, tích hợp và kiểm tra chức năng.

Nếu bạn có thể thay đổi mã của mình và các bài kiểm tra của bạn sẽ không bị hỏng, điều đó sẽ gây nghi ngờ cho tôi. Sự khác biệt giữa một thay đổi hợp pháp và một lỗi chỉ là thực tế là nó được yêu cầu, một yêu cầu được xác định là (giả định TDD) được xác định bởi các thử nghiệm của bạn.

dữ liệu đã được mã hóa cứng.

Dữ liệu được mã hóa cứng trong các bài kiểm tra là một điều tốt. Các xét nghiệm làm việc như giả mạo, không phải là bằng chứng. Nếu có quá nhiều tính toán, các bài kiểm tra của bạn có thể là tautology. Ví dụ:

assert sum([1,2,3]) == 6
assert sum([1,2,3]) == 1 + 2 + 3
assert sum([1,2,3]) == reduce(operator.add, [1,2,3])

Độ trừu tượng càng cao, bạn càng tiến gần đến thuật toán và do đó, càng gần để so sánh việc thực hiện chính xác với chính nó.

rất ít sử dụng lại mã

Việc sử dụng lại mã tốt nhất trong các bài kiểm tra là imho 'Kiểm tra', như trong jUnits assertThat, vì chúng giữ cho các bài kiểm tra đơn giản. Ngoài ra, nếu các thử nghiệm có thể được tái cấu trúc để chia sẻ mã, thì mã thực tế được thử nghiệm cũng có thể cũng vậy , do đó giảm các thử nghiệm đối với các thử nghiệm thử nghiệm cơ sở được tái cấu trúc.


Tôi muốn biết nơi downvoter không đồng ý.
keppla

keppla - Tôi không phải là người downvoter, nhưng nói chung, tùy thuộc vào nơi tôi đang ở trong mô hình, tôi thích thử nghiệm tương tác đối tượng hơn dữ liệu thử nghiệm ở cấp độ đơn vị. Kiểm tra dữ liệu hoạt động tốt hơn ở cấp độ tích hợp.
Ritch Melton

@keppla Tôi có một lớp định tuyến một đơn đặt hàng đến một kênh khác nếu tổng số mặt hàng của nó có chứa các mặt hàng bị hạn chế nhất định. Tôi tạo một đơn đặt hàng giả tạo với 4 mục trong đó hai mục bị hạn chế. Đối với các mục bị hạn chế được thêm vào, bài kiểm tra này là duy nhất. Nhưng các bước tạo đơn hàng giả và thêm hai mục thông thường là cùng một thiết lập mà một bài kiểm tra khác sử dụng để kiểm tra quy trình làm việc của mục không bị hạn chế. Trong trường hợp này cùng với các mục nếu đơn đặt hàng cần phải thiết lập dữ liệu khách hàng và thiết lập địa chỉ, v.v. đây không phải là trường hợp tốt của việc sử dụng lại các trợ giúp thiết lập. Tại sao chỉ khẳng định tái sử dụng?
Asif Shiraz

6

Tôi cũng có vấn đề này. Cách tiếp cận cải tiến của tôi đã được như sau:

  1. Đừng viết bài kiểm tra đơn vị trừ khi chúng là cách tốt nhất để kiểm tra thứ gì đó.

    Tôi đã chuẩn bị đầy đủ để thừa nhận rằng các xét nghiệm đơn vị có chi phí chẩn đoán và thời gian sửa chữa thấp nhất. Điều này làm cho chúng trở thành một công cụ có giá trị. Vấn đề là, rõ ràng là số dặm của bạn có thể thay đổi, các bài kiểm tra đơn vị thường quá nhỏ để có thể chi phí duy trì khối lượng mã. Tôi đã viết một ví dụ ở phía dưới, có một cái nhìn.

  2. Sử dụng các xác nhận bất cứ nơi nào chúng tương đương với thử nghiệm đơn vị cho thành phần đó. Các xác nhận có thuộc tính tốt mà chúng luôn được xác minh trong suốt quá trình gỡ lỗi. Vì vậy, thay vì kiểm tra các ràng buộc của lớp "Nhân viên" trong một đơn vị kiểm tra riêng biệt, bạn đang kiểm tra hiệu quả lớp Nhân viên thông qua mọi trường hợp kiểm tra trong hệ thống. Các xác nhận cũng có một đặc tính tốt là chúng không tăng khối lượng mã nhiều như các thử nghiệm đơn vị (cuối cùng yêu cầu giàn giáo / chế nhạo / bất cứ điều gì).

    Trước khi ai đó giết tôi: các bản dựng sản xuất không nên sụp đổ trên các xác nhận. Thay vào đó, họ nên đăng nhập ở cấp độ "Lỗi".

    Để cảnh báo cho những người chưa nghĩ về nó, đừng khẳng định bất cứ điều gì về đầu vào của người dùng hoặc mạng. Đó là một sai lầm lớn ™.

    Trong các cơ sở mã mới nhất của tôi, tôi đã thận trọng loại bỏ các bài kiểm tra đơn vị bất cứ nơi nào tôi thấy một cơ hội rõ ràng để xác nhận. Điều này đã giảm đáng kể chi phí bảo trì tổng thể và làm cho tôi trở thành một người hạnh phúc hơn nhiều.

  3. Thích kiểm tra hệ thống / tích hợp, triển khai chúng cho tất cả các luồng chính và trải nghiệm người dùng của bạn. Trường hợp góc có lẽ không cần phải ở đây. Kiểm tra hệ thống xác minh hành vi ở cuối người dùng bằng cách chạy tất cả các thành phần. Do đó, một bài kiểm tra hệ thống nhất thiết phải chậm hơn, vì vậy hãy viết những bài quan trọng (không hơn, không kém) và bạn sẽ nắm bắt được những vấn đề quan trọng nhất. Kiểm tra hệ thống có chi phí bảo trì rất thấp.

    Điều quan trọng cần nhớ là, vì bạn đang sử dụng các xác nhận, mỗi bài kiểm tra hệ thống sẽ chạy vài trăm "bài kiểm tra đơn vị" cùng một lúc. Bạn cũng khá yên tâm rằng những cái quan trọng nhất sẽ được chạy nhiều lần.

  4. Viết các API mạnh có thể được kiểm tra chức năng. Các kiểm tra chức năng rất khó xử và (hãy đối mặt với nó) là vô nghĩa nếu API của bạn làm cho quá khó để tự mình xác minh các thành phần chức năng. Thiết kế API tốt a) làm cho các bước kiểm tra trở nên đơn giản và b) đưa ra các xác nhận rõ ràng và có giá trị.

    Kiểm tra chức năng là điều khó nhất để có được quyền, đặc biệt là khi bạn có các thành phần giao tiếp một-nhiều hoặc (thậm chí tệ hơn, trời ơi) nhiều rào cản trong quá trình. Càng nhiều đầu vào và đầu ra gắn liền với một thành phần, kiểm tra chức năng càng khó, bởi vì bạn phải cách ly một trong số chúng để thực sự kiểm tra chức năng của nó.


Về vấn đề "không viết bài kiểm tra đơn vị", tôi sẽ trình bày một ví dụ:

TEST(exception_thrown_on_null)
{
    InternalDataStructureType sink;
    ASSERT_THROWS(sink.consumeFrom(NULL), std::logic_error);
    try {
        sink.consumeFrom(NULL);
    } catch (const std::logic_error& e) {
        ASSERT(e.what() == "You must not pass NULL as a parameter!");
    }
}

Người viết bài kiểm tra này đã thêm bảy dòng hoàn toàn không đóng góp vào việc xác minh sản phẩm cuối cùng. Người dùng sẽ không bao giờ thấy điều này xảy ra, vì a) không ai nên vượt qua NULL ở đó (vì vậy hãy viết một xác nhận, sau đó) hoặc b) trường hợp NULL sẽ gây ra một số hành vi khác nhau. Nếu trường hợp là (b), hãy viết một bài kiểm tra thực sự xác minh hành vi đó.

Triết lý của tôi đã trở thành rằng chúng ta không nên thử nghiệm các tạo tác thực hiện. Chúng ta chỉ nên kiểm tra bất cứ điều gì có thể được coi là một đầu ra thực tế. Mặt khác, không có cách nào để tránh viết hai lần khối lượng cơ bản giữa các bài kiểm tra đơn vị (bắt buộc phải thực hiện cụ thể) và bản thân việc thực hiện.

Điều quan trọng cần lưu ý, ở đây, có những ứng cử viên tốt cho các bài kiểm tra đơn vị. Trong thực tế, thậm chí có một số tình huống trong đó một bài kiểm tra đơn vị là phương tiện thích hợp duy nhất để xác minh một cái gì đó và trong đó nó có giá trị cao để viết và duy trì các bài kiểm tra đó. Trên đỉnh đầu của tôi, danh sách này bao gồm các thuật toán không cần thiết, các thùng chứa dữ liệu được hiển thị trong một API và mã được tối ưu hóa cao có vẻ "phức tạp" (hay còn gọi là "người tiếp theo có thể sẽ làm hỏng nó.").

Sau đó, lời khuyên cụ thể của tôi dành cho bạn: Bắt đầu xóa các bài kiểm tra đơn vị một cách thận trọng khi chúng bị hỏng, tự hỏi mình câu hỏi "đây có phải là một đầu ra không, hay tôi đang lãng phí mã?" Bạn có thể sẽ thành công trong việc giảm số lượng những thứ đang lãng phí thời gian của bạn.


3
Thích các bài kiểm tra hệ thống / tích hợp - Điều này thật tồi tệ. Hệ thống của bạn đạt đến điểm sử dụng các bài kiểm tra (chậm!) Này để kiểm tra những thứ có thể bắt được nhanh chóng ở cấp độ đơn vị và phải mất hàng giờ để chúng chạy vì bạn có quá nhiều bài kiểm tra tương tự và chậm.
Ritch Melton

1
@RitchMelton Hoàn toàn tách biệt với cuộc thảo luận, có vẻ như bạn cần một máy chủ CI mới. CI không nên cư xử như vậy.
Andres Jaan Tack

1
Một chương trình bị lỗi (đó là những gì xác nhận làm) không nên giết người chạy thử nghiệm (CI) của bạn. Đó là lý do tại sao bạn có một người chạy thử; vì vậy một cái gì đó có thể phát hiện và báo cáo những thất bại như vậy.
Andres Jaan Tack

1
Các xác nhận theo kiểu 'Assert' chỉ gỡ lỗi mà tôi quen thuộc (không phải các xác nhận kiểm tra) bật lên một hộp thoại treo CI vì nó đang chờ tương tác của nhà phát triển.
Ritch Melton

1
Ah, điều đó sẽ giải thích rất nhiều về sự bất đồng của chúng tôi. :) Tôi đang đề cập đến khẳng định kiểu C. Bây giờ tôi chỉ nhận thấy rằng đây là một câu hỏi .NET. cplusplus.com/reference/cl Library / cassert / assert
Andres Jaan Tack

5

Dường như với tôi như thử nghiệm đơn vị của bạn hoạt động như một nét duyên dáng. Một điều tốt là nó rất mong manh để thay đổi, vì đó là loại toàn bộ vấn đề. Những thay đổi nhỏ trong kiểm tra ngắt mã để bạn có thể loại trừ khả năng xảy ra lỗi trong suốt chương trình của mình.

Tuy nhiên, hãy nhớ rằng bạn chỉ thực sự cần phải thử nghiệm các điều kiện sẽ khiến phương pháp của bạn thất bại hoặc cho kết quả không mong muốn. Điều này sẽ giữ cho đơn vị kiểm tra của bạn dễ bị "phá vỡ" hơn nếu có một vấn đề thực sự hơn là những điều nhỏ nhặt.

Mặc dù đối với tôi có vẻ như bạn đang thiết kế lại chương trình rất nhiều. Trong những trường hợp như vậy, hãy làm bất cứ điều gì bạn cần và loại bỏ các bài kiểm tra cũ và thay thế chúng bằng các bài kiểm tra mới sau đó. Sửa chữa các bài kiểm tra đơn vị chỉ đáng giá nếu bạn không sửa chữa do những thay đổi căn bản trong chương trình của bạn. Mặt khác, bạn có thể thấy rằng bạn dành quá nhiều thời gian cho việc viết lại các bài kiểm tra để có thể áp dụng trong phần mã chương trình mới được viết của bạn.


3

Tôi chắc chắn những người khác sẽ có nhiều đầu vào hơn, nhưng theo kinh nghiệm của tôi, đây là một số điều quan trọng sẽ giúp bạn:

  1. Sử dụng một nhà máy đối tượng thử nghiệm để xây dựng các cấu trúc dữ liệu đầu vào, do đó bạn không cần phải sao chép logic đó. Có lẽ nhìn vào một thư viện trợ giúp như AutoFixture để cắt giảm mã cần thiết cho thiết lập thử nghiệm.
  2. Đối với mỗi lớp kiểm tra, tập trung vào việc tạo SUT, do đó sẽ dễ dàng thay đổi khi mọi thứ được tái cấu trúc.
  3. Hãy nhớ rằng, mã kiểm tra cũng quan trọng như mã sản xuất. Nó cũng nên được cấu trúc lại, nếu bạn thấy rằng bạn đang lặp lại chính mình, nếu mã cảm thấy không thể đo được, v.v., v.v.

Bạn càng sử dụng lại mã qua các bài kiểm tra, chúng càng trở nên dễ vỡ hơn , bởi vì bây giờ thay đổi một bài kiểm tra có thể phá vỡ một bài kiểm tra khác. Đó có thể là một chi phí hợp lý, để đổi lấy khả năng duy trì - tôi không tham gia vào cuộc tranh luận đó ở đây - nhưng để tranh luận rằng các điểm 1 và 2 làm cho các bài kiểm tra trở nên mong manh hơn (đó là câu hỏi) chỉ là sai.
pdr

@driis - Phải, mã kiểm tra có thành ngữ khác với mã chạy. Che giấu mọi thứ bằng cách tái cấu trúc mã 'chung' và sử dụng các công cụ như bộ chứa IoC chỉ che giấu các vấn đề thiết kế bị phơi bày bởi các thử nghiệm của bạn.
Ritch Melton

Mặc dù điểm @pdr đưa ra có khả năng hợp lệ cho các bài kiểm tra đơn vị, tôi cho rằng đối với các bài kiểm tra tích hợp / hệ thống, có thể hữu ích khi nghĩ theo cách "chuẩn bị ứng dụng cho nhiệm vụ X". Điều đó có thể liên quan đến việc điều hướng đến vị trí thích hợp, cài đặt các cài đặt thời gian chạy nhất định, mở tệp dữ liệu, v.v. Nếu nhiều thử nghiệm tích hợp bắt đầu ở cùng một nơi, tái cấu trúc mã đó để sử dụng lại nó qua nhiều thử nghiệm có thể không phải là điều xấu nếu bạn hiểu các rủi ro và hạn chế của cách tiếp cận đó.
một CVn

2

Xử lý các bài kiểm tra như bạn làm điều đó với mã nguồn.

Kiểm soát phiên bản, phát hành điểm kiểm tra, theo dõi vấn đề, "quyền sở hữu tính năng", lập kế hoạch và ước tính nỗ lực, v.v. Đã làm điều đó - Tôi nghĩ đây là cách hiệu quả nhất để xử lý các vấn đề như bạn mô tả.


1

Bạn chắc chắn nên xem qua các mẫu thử XUnit của Gerard Meszaros . Nó có một phần tuyệt vời với nhiều công thức để sử dụng lại mã kiểm tra của bạn và tránh trùng lặp.

Nếu các bài kiểm tra của bạn dễ vỡ, thì cũng có thể là bạn không đủ khả năng để kiểm tra gấp đôi. Đặc biệt, nếu bạn tạo lại toàn bộ biểu đồ của các đối tượng khi bắt đầu mỗi bài kiểm tra đơn vị, các phần Sắp xếp trong các bài kiểm tra của bạn có thể trở nên quá khổ và bạn có thể thường thấy mình trong các tình huống phải viết lại các phần Sắp xếp trong một số lượng đáng kể các bài kiểm tra chỉ vì một trong những lớp được sử dụng phổ biến nhất của bạn đã thay đổi. Giả và cuống có thể giúp bạn ở đây bằng cách cắt giảm số lượng đối tượng bạn phải bù nước để có bối cảnh thử nghiệm có liên quan.

Lấy các chi tiết không quan trọng ra khỏi các thiết lập thử nghiệm của bạn thông qua các giả và sơ khai và áp dụng các mẫu thử nghiệm để sử dụng lại mã sẽ làm giảm đáng kể độ mong manh của chúng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.