Là khẳng định hoặc kiểm tra đơn vị quan trọng hơn?


51

Cả hai khẳng định và kiểm tra đơn vị đều đóng vai trò là tài liệu cho một cơ sở mã hóa và là phương tiện để phát hiện ra các lỗi. Sự khác biệt chính là khẳng định chức năng như kiểm tra độ tỉnh táo và xem các đầu vào thực, trong khi các thử nghiệm đơn vị chạy trên các đầu vào mô phỏng cụ thể và là các thử nghiệm đối với một "câu trả lời đúng" được xác định rõ. Các giá trị tương đối của việc sử dụng các khẳng định so với các bài kiểm tra đơn vị là phương tiện chính để xác minh tính đúng đắn là gì? Mà bạn tin rằng nên được nhấn mạnh hơn?


4
Tôi thích ý tưởng này rất nhiều ... đến nỗi tôi đang biến chủ đề thành bài tập cho khóa học kiểm tra phần mềm và kiểm tra chất lượng của mình. :-)
Macneil

Một câu hỏi khác là: bạn có nên kiểm tra đơn vị của mình không? ;)
mojuba

Câu trả lời:


43

Các xác nhận rất hữu ích để cho bạn biết về trạng thái nội bộ của chương trình . Ví dụ: cấu trúc dữ liệu của bạn có trạng thái hợp lệ, ví dụ: Timecấu trúc dữ liệu sẽ không giữ giá trị của 25:61:61. Các điều kiện được kiểm tra bởi các khẳng định là:

  • Điều kiện tiên quyết, đảm bảo rằng người gọi giữ hợp đồng của mình,

  • Postconditions, đảm bảo rằng callee giữ hợp đồng của nó, và

  • Các bất biến, đảm bảo rằng cấu trúc dữ liệu luôn giữ một số thuộc tính sau khi hàm trả về. Một bất biến là một điều kiện là điều kiện tiên quyết và hậu điều kiện.

Kiểm tra đơn vị rất hữu ích để cho bạn biết về hành vi bên ngoài của mô-đun . Bạn Stackcó thể có trạng thái nhất quán sau khi push()phương thức được gọi, nhưng nếu kích thước của ngăn xếp không tăng gấp ba sau khi nó được gọi ba lần, thì đó là một lỗi. (Ví dụ: trường hợp tầm thường trong đó việc push()triển khai không chính xác chỉ kiểm tra các xác nhận và thoát.)

Nói một cách chính xác, sự khác biệt chính giữa các xác nhận và kiểm tra đơn vị là các kiểm tra đơn vị có dữ liệu kiểm tra (các giá trị để chương trình chạy), trong khi các xác nhận thì không. Đó là, bạn có thể thực hiện các bài kiểm tra đơn vị của mình một cách tự động, trong khi bạn không thể nói tương tự cho các xác nhận. Vì lợi ích của cuộc thảo luận này, tôi đã giả định rằng bạn đang nói về việc thực hiện chương trình trong bối cảnh kiểm tra chức năng bậc cao (thực thi toàn bộ chương trình và không lái các mô-đun như kiểm tra đơn vị). Nếu bạn không nói về kiểm tra chức năng tự động như là phương tiện để "xem đầu vào thực", thì rõ ràng giá trị nằm ở tự động hóa, và do đó, kiểm tra đơn vị sẽ giành chiến thắng. Nếu bạn đang nói về điều này trong bối cảnh kiểm tra chức năng (tự động), thì xem bên dưới.

Có thể có một số chồng chéo trong những gì đang được thử nghiệm. Ví dụ, Stackpostcondition thực sự có thể khẳng định rằng kích thước ngăn xếp tăng thêm một. Nhưng có những giới hạn đối với những gì có thể được thực hiện trong khẳng định đó: Nó cũng nên kiểm tra xem phần tử trên cùng có phải là những gì vừa được thêm vào không?

Đối với cả hai, mục tiêu là tăng chất lượng. Đối với thử nghiệm đơn vị, mục tiêu là tìm lỗi. Đối với các xác nhận, mục tiêu là làm cho việc gỡ lỗi dễ dàng hơn bằng cách quan sát các trạng thái chương trình không hợp lệ ngay khi chúng xảy ra.

Lưu ý rằng không có kỹ thuật xác minh tính chính xác. Trong thực tế, nếu bạn tiến hành kiểm tra đơn vị với mục tiêu để xác minh chương trình là chính xác, bạn có thể sẽ đưa ra thử nghiệm không thú vị mà bạn biết sẽ hoạt động. Đó là một hiệu ứng tâm lý: bạn sẽ làm bất cứ điều gì để đáp ứng mục tiêu của bạn. Nếu mục tiêu của bạn là tìm lỗi, các hoạt động của bạn sẽ phản ánh điều đó.

Cả hai đều quan trọng, và có mục đích riêng của họ.

[Là một lưu ý cuối cùng về các xác nhận: Để có được giá trị cao nhất, bạn cần sử dụng chúng tại tất cả các điểm quan trọng trong chương trình của bạn và không phải là một vài chức năng chính. Mặt khác, nguồn gốc của vấn đề có thể đã bị che giấu và khó phát hiện mà không cần gỡ lỗi hàng giờ.]


7

Khi nói về các xác nhận, hãy nhớ rằng chúng có thể được tắt trong một cái công tắc.

Ví dụ về một khẳng định rất xấu:

char *c = malloc(1024);
assert(c != NULL);

Tại sao điều này là xấu? Bởi vì không có kiểm tra lỗi được thực hiện nếu xác nhận đó bị bỏ qua do một cái gì đó giống như NDEBUG được xác định.

Một bài kiểm tra đơn vị sẽ (có thể) chỉ là segfault trên đoạn mã trên. Chắc chắn, nó đã làm công việc của nó bằng cách nói với bạn rằng một cái gì đó đã đi sai, hoặc đã làm nó? Làm thế nào có khả năng là malloc()thất bại trong bài kiểm tra?

Các xác nhận là dành cho mục đích gỡ lỗi khi lập trình viên cần ngụ ý rằng không có sự kiện 'bình thường' nào sẽ khiến cho xác nhận được kích hoạt. malloc()thất bại là, thực sự là một sự kiện bình thường, do đó không bao giờ nên được khẳng định.

Có nhiều trường hợp khác mà các xác nhận được sử dụng thay vì xử lý thỏa đáng những điều có thể sai. Đây là lý do tại sao các xác nhận có tiếng xấu và tại sao các ngôn ngữ như Go không bao gồm chúng.

Kiểm tra đơn vị được thiết kế để cho bạn biết khi một cái gì đó bạn thay đổi đã phá vỡ một cái gì đó khác. Chúng được thiết kế để giúp bạn (trong hầu hết các trường hợp) tránh khỏi mọi tính năng của chương trình (tuy nhiên, người kiểm tra rất quan trọng đối với các bản phát hành) mỗi khi bạn tạo bản dựng.

Thực sự không có bất kỳ mối tương quan rõ rệt nào giữa hai người, ngoài cả hai đều nói với bạn rằng có điều gì đó không ổn. Hãy nghĩ về một khẳng định như một điểm dừng trong một cái gì đó bạn đang làm việc, mà không phải sử dụng một trình gỡ lỗi. Hãy nghĩ về một bài kiểm tra đơn vị như một cái gì đó cho bạn biết nếu bạn đã phá vỡ một cái gì đó mà bạn không làm việc.


3
Đó là lý do tại sao các xác nhận luôn được coi là tuyên bố đúng, không phải kiểm tra lỗi.
Dominique McDonnell

@DominicMcDonnell Vâng, 'nên là sự thật'. Đôi khi tôi khẳng định để giải quyết các vấn đề về trình biên dịch, chẳng hạn như các phiên bản gcc nhất định có tích hợp lỗi (). Điều quan trọng cần nhớ là các bản dựng sản xuất nên tắt chúng đi .
Tim Post

Và ở đây tôi nghĩ mã sản xuất là nơi cần khẳng định nhất , bởi vì đó là trong sản xuất, nơi bạn sẽ nhận được đầu vào mà bạn không nghĩ là có thể. Sản xuất là nơi đẩy tất cả các lỗi khó nhất ra ngoài.
Frank Shearar

@Frank Shearar, rất đúng. Bạn vẫn sẽ thất bại nặng nề ở trạng thái lỗi được phát hiện sớm nhất trong sản xuất. Chắc chắn người dùng sẽ phàn nàn, nhưng đó là cách duy nhất bạn sẽ đảm bảo rằng các lỗi sẽ được sửa. Và sẽ tốt hơn rất nhiều khi nhận được blah bằng 0 so với ngoại lệ bộ nhớ cho việc hủy bỏ hội thảo null một vài cuộc gọi chức năng sau đó.
Dominique McDonnell

Tại sao không tốt hơn để xử lý các vấn đề điển hình nhưng thường không phổ biến (trong mắt người dùng) thay vì khẳng định rằng chúng không nên xảy ra? Tôi không nhận ra sự khôn ngoan này. Chắc chắn, khẳng định rằng một trình tạo nguyên tố trả về một số nguyên tố, cần một chút công việc phụ, dự kiến ​​trong các bản dựng gỡ lỗi. Khẳng định một cái gì đó mà một ngôn ngữ có thể kiểm tra tự nhiên là chính xác, ngu ngốc. Không gói các thử nghiệm đó trong một công tắc khác có thể bị tắt một vài tháng sau khi phát hành, thậm chí còn ngu ngốc hơn.
Tim Post

5

Chúng là cả hai công cụ được sử dụng để giúp cải thiện chất lượng tổng thể của hệ thống bạn đang xây dựng. Rất nhiều tùy thuộc vào ngôn ngữ bạn đang sử dụng, loại ứng dụng bạn đang xây dựng và thời gian bạn sử dụng tốt nhất. Chưa kể bạn có một vài trường phái suy nghĩ về nó.

Để bắt đầu, nếu bạn đang sử dụng ngôn ngữ không có asserttừ khóa, bạn không thể sử dụng các xác nhận (ít nhất là không theo cách chúng ta đang nói ở đây). Trong một thời gian dài, Java không có asserttừ khóa và một số ngôn ngữ vẫn không có. Kiểm tra đơn vị sau đó trở nên khá quan trọng hơn một chút. Trong một số ngôn ngữ, các xác nhận chỉ được chạy khi cờ được đặt (một lần nữa với Java ở đây). Khi các biện pháp bảo vệ không phải lúc nào cũng ở đó, đó không phải là một tính năng rất hữu ích.

Có một trường phái cho rằng nếu bạn đang "khẳng định" điều gì đó, bạn cũng có thể viết một khối ngoại lệ if/ throwcó ý nghĩa. Quá trình suy nghĩ này xuất phát từ nhiều khẳng định được đặt vào đầu một phương thức để đảm bảo tất cả các giá trị nằm trong giới hạn. Kiểm tra các điều kiện tiên quyết của bạn là một phần rất quan trọng để có một postcondition dự kiến.

Kiểm thử đơn vị là mã bổ sung phải được viết và duy trì. Đối với nhiều người đây là một nhược điểm. Tuy nhiên, với các khung kiểm thử đơn vị hiện tại, bạn có thể tạo ra số lượng điều kiện kiểm thử lớn hơn với mã tương đối ít. Các thử nghiệm được tham số hóa và "lý thuyết" sẽ thực hiện cùng một thử nghiệm với một số lượng lớn các mẫu dữ liệu có thể phát hiện ra một số lỗi khó tìm.

Cá nhân tôi thấy rằng tôi nhận được nhiều dặm hơn với thử nghiệm đơn vị hơn là rắc các xác nhận, nhưng đó là do các nền tảng tôi phát triển trên hầu hết thời gian (Java / C #). Các ngôn ngữ khác có hỗ trợ xác nhận mạnh mẽ hơn và thậm chí "Thiết kế theo hợp đồng" (xem bên dưới) để cung cấp nhiều đảm bảo hơn nữa. Nếu tôi đang làm việc với một trong những ngôn ngữ này, tôi có thể sử dụng DBC nhiều hơn thử nghiệm đơn vị.

http://en.wikipedia.org/wiki/Design_by_contract#Lacular_with_native_support

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.