Có lý do nào mà các bài kiểm tra không được viết nội tuyến với mã mà chúng kiểm tra không?


91

Gần đây tôi đã đọc một chút về Lập trình biết chữ và nó khiến tôi suy nghĩ ... Các bài kiểm tra được viết tốt, đặc biệt là thông số kỹ thuật theo kiểu BDD có thể làm tốt hơn việc giải thích mã nào hơn văn xuôi và có lợi thế lớn là kiểm chứng tính chính xác của họ

Tôi chưa bao giờ thấy các bài kiểm tra viết nội tuyến với mã mà họ kiểm tra. Có phải chỉ vì các ngôn ngữ không có xu hướng đơn giản để tách mã ứng dụng và mã kiểm tra khi được viết trong cùng một tệp nguồn (và không ai làm cho nó dễ dàng), hay có lý do nào nguyên tắc hơn khi mọi người tách mã kiểm tra khỏi mã ứng dụng?


33
Một số ngôn ngữ lập trình như python với doctest cho phép bạn làm điều đó.
Simon Bergot

2
Bạn có thể cảm thấy thông số kỹ thuật theo kiểu BDD tốt hơn văn xuôi trong việc giải thích mã, nhưng điều đó không có nghĩa là sự kết hợp của cả hai không tốt hơn.
JeffO

5
Một nửa trong số các đối số ở đây cũng áp dụng cho tài liệu nội tuyến.
CodeInChaos

3
@Simon doctests quá đơn giản để thử nghiệm nghiêm trọng, chủ yếu là vì chúng không được thiết kế cho nó. Chúng được dự định và excel tại, có các ví dụ mã trong tài liệu có thể được xác minh tự động. Bây giờ, một số người cũng sử dụng chúng để thử nghiệm đơn vị, nhưng gần đây (như trong những năm qua), điều này đã gây ra rất nhiều khó khăn, bởi vì nó có xu hướng kết thúc trong những mớ hỗn độn, "tài liệu" quá dài và những mớ hỗn độn khác.

7
Thiết kế theo Hợp đồng cho phép các thông số kỹ thuật nội tuyến giúp thử nghiệm đơn giản.
Fuhrmanator

Câu trả lời:


89

Ưu điểm duy nhất tôi có thể nghĩ đến cho các bài kiểm tra nội tuyến là giảm số lượng tệp được ghi. Với các IDE hiện đại, điều này thực sự không phải là vấn đề lớn.

Tuy nhiên, có một số nhược điểm rõ ràng đối với thử nghiệm nội tuyến:

  • Nó vi phạm sự phân tách các mối quan tâm . Điều này có thể gây tranh cãi, nhưng đối với tôi, chức năng kiểm tra là một trách nhiệm khác với việc thực hiện nó.
  • Bạn sẽ phải giới thiệu các tính năng ngôn ngữ mới để phân biệt giữa các thử nghiệm / triển khai hoặc bạn có nguy cơ làm mờ ranh giới giữa hai thử nghiệm.
  • Các tệp nguồn lớn hơn khó làm việc hơn: khó đọc hơn, khó hiểu hơn, bạn có nhiều khả năng phải giải quyết các xung đột kiểm soát nguồn.
  • Tôi nghĩ rằng sẽ khó khăn hơn khi đội chiếc mũ "người thử nghiệm" của bạn lên, để nói. Nếu bạn đang xem chi tiết triển khai, bạn sẽ bị bỏ qua việc thực hiện các thử nghiệm nhất định.

9
Nó thật thú vị. Tôi đoán lợi thế tôi có thể thấy là khi bạn đội chiếc mũ "coder" của mình, bạn muốn nghĩ về các bài kiểm tra, nhưng một điểm tốt là điều ngược lại là không đúng.
Chris Devereux

2
Dọc theo những dòng này, có thể (và có lẽ mong muốn) có một người tạo ra các thử nghiệm và một giây thực sự thực hiện mã. Đặt các bài kiểm tra nội tuyến làm cho điều này khó khăn hơn.
Jim Nutt

6
sẽ downvote nếu tôi có thể. Làm thế nào đây là một câu trả lời theo bất kỳ cách nào cả? Người thực hiện không viết bài kiểm tra? Mọi người bỏ qua các bài kiểm tra nếu họ nhìn vào chi tiết thực hiện? "Chỉ là quá khó" Xung đột trên các tập tin lớn ?? ANd làm thế nào để một bài kiểm tra có thể bị nhầm lẫn với một chi tiết thực hiện ???
bharal

5
@bharal Ngoài ra, viết cho "Chỉ là quá khó", khổ dâm là đức tính ngu ngốc. Tôi muốn mọi thứ trở nên dễ dàng ngoại trừ vấn đề tôi thực sự đang cố gắng giải quyết.
deworde

3
Kiểm tra đơn vị có thể được coi là tài liệu. Điều đó cho thấy các bài kiểm tra đơn vị nên được đưa vào mã với cùng lý do như các bình luận - để cải thiện khả năng đọc. Tuy nhiên, vấn đề với đó là có rất nhiều bài kiểm tra đơn vị và rất nhiều chi phí thực hiện kiểm tra không chỉ định kết quả mong đợi. Ngay cả các bình luận trong mã cũng phải được giữ gọn gàng, với các giải thích lớn hơn được đưa ra ngoài - đến một khối bình luận bên ngoài chức năng, đến một tệp riêng biệt hoặc có thể vào một tài liệu thiết kế. Các bài kiểm tra đơn vị là IMO hiếm khi nếu đủ ngắn để giữ mã được kiểm tra như nhận xét.
Steve314

36

Tôi có thể nghĩ về một số:

  • Dễ đọc. Việc xen kẽ mã "thực" và kiểm tra sẽ khiến việc đọc mã thực khó hơn.

  • Mã phình to. Trộn mã "thực" và mã kiểm tra vào cùng một tệp / lớp / bất cứ điều gì có khả năng dẫn đến các tệp được biên dịch lớn hơn, v.v ... Điều này đặc biệt quan trọng đối với các ngôn ngữ có ràng buộc muộn.

  • Bạn có thể không muốn khách hàng / khách hàng của bạn nhìn thấy mã kiểm tra của bạn. (Tôi không thích lý do này ... nhưng nếu bạn đang làm việc trong một dự án nguồn đóng, mã kiểm tra không có khả năng giúp khách hàng bằng mọi cách.)

Bây giờ có cách giải quyết có thể cho từng vấn đề này. Nhưng IMO, đơn giản hơn là không đến đó ngay từ đầu.


Điều đáng quan sát là trong những ngày đầu, các lập trình viên Java thường làm việc này; ví dụ bao gồm một main(...)phương thức trong một lớp để tạo điều kiện kiểm tra. Ý tưởng này đã gần như biến mất hoàn toàn. Đó là thực tế công nghiệp để thực hiện các thử nghiệm riêng biệt bằng cách sử dụng một khung kiểm tra của một số loại.

Cũng đáng để quan sát rằng Lập trình Văn học (như Knuth nghĩ ra) chưa bao giờ bị cuốn vào ngành công nghiệp phần mềm.


4
+1 Các vấn đề về khả năng đọc - mã kiểm tra có thể lớn hơn tương ứng với mã triển khai, đặc biệt là trong các thiết kế OO.
Fuhrmanator

2
+1 để chỉ ra bằng cách sử dụng các khung kiểm tra. Tôi không thể tưởng tượng việc sử dụng một khung kiểm tra tốt đồng thời với mã sản xuất.
joshin4colours

1
RE: Bạn có thể không muốn khách hàng / khách hàng của mình thấy mã kiểm tra của bạn. (Tôi không thích lý do này ... nhưng nếu bạn đang làm việc trong một dự án nguồn đóng, mã kiểm tra không có khả năng giúp khách hàng bằng mọi cách.) - Có thể nên chạy thử nghiệm trên máy khách. Chạy các bài kiểm tra có thể giúp nhanh chóng xác định vấn đề là gì và sự khác biệt về id trong các khách hàng env ..
sixty feetersdude

1
@sixtyfootersdude - đó là một tình huống khá bất thường. Và giả sử rằng bạn đang phát triển nguồn đóng, bạn sẽ không muốn đưa các thử nghiệm của mình vào bản phân phối nhị phân tiêu chuẩn của mình chỉ trong trường hợp. (Bạn sẽ tạo một gói riêng có chứa các bài kiểm tra mà bạn muốn khách hàng chạy.)
Stephen C

1
1) Bạn có bỏ lỡ phần đầu tiên trong câu trả lời của tôi không, nơi tôi đã đưa ra ba lý do thực tế? Có một số "ý nghĩ phê phán" liên quan đến đó .... 2) Bạn có bỏ lỡ phần thứ hai mà tôi đã nói rằng các lập trình viên Java đã từng làm điều này, nhưng bây giờ họ không? Và hàm ý rõ ràng rằng các lập trình viên đã ngừng làm điều này ... vì lý do chính đáng?
Stephen C

14

Trên thực tế, bạn có thể nghĩ về Design By Contract là làm điều này. Vấn đề là hầu hết các ngôn ngữ lập trình không cho phép bạn viết mã như thế này :( Rất dễ dàng để kiểm tra các điều kiện tiên quyết bằng tay, nhưng điều kiện bài đăng là một thách thức thực sự mà không thay đổi cách bạn viết mã (IMO âm rất lớn).

Michael Feathers có một bài thuyết trình về điều này và đây là một trong nhiều cách anh ấy đề cập đến bạn có thể cải thiện chất lượng mã.


13

Vì nhiều lý do tương tự mà bạn cố gắng tránh khớp nối chặt chẽ giữa các lớp trong mã của mình, đó cũng là một ý tưởng tốt để tránh ghép nối không cần thiết giữa các thử nghiệm và mã.

Sáng tạo: Các thử nghiệm và mã có thể được viết vào những thời điểm khác nhau, bởi những người khác nhau.

Kiểm soát: Nếu các kiểm tra được sử dụng để chỉ định các yêu cầu, chắc chắn bạn muốn chúng phải tuân theo các quy tắc khác nhau về việc ai có thể thay đổi chúng và khi nào hơn mã thực tế.

Khả năng sử dụng lại: Nếu bạn đặt các bài kiểm tra nội tuyến, bạn không thể sử dụng chúng với một đoạn mã khác.

Hãy tưởng tượng rằng bạn đã có một đoạn mã thực hiện công việc một cách chính xác, nhưng để lại rất nhiều điều mong muốn về hiệu suất, khả năng bảo trì, bất cứ điều gì. Bạn quyết định thay thế mã đó bằng mã mới và cải tiến. Sử dụng cùng một bộ kiểm tra có thể giúp bạn xác minh rằng mã mới tạo ra kết quả giống như mã cũ.

Khả năng lựa chọn: Giữ các bài kiểm tra tách biệt với mã giúp bạn dễ dàng chọn bài kiểm tra nào bạn muốn chạy.

Ví dụ: bạn có thể có một bộ thử nghiệm nhỏ liên quan đến mã bạn đang làm việc và một bộ lớn hơn kiểm tra toàn bộ dự án.


Tôi bối rối về lý do của bạn: TDD đã nói rằng việc tạo thử nghiệm xảy ra trước (hoặc cùng lúc) với mã sản xuất và phải được thực hiện bởi cùng một lập trình viên! Họ cũng gợi ý rằng các bài kiểm tra khá giống như yêu cầu. Tất nhiên, những phản đối này không áp dụng nếu bạn không đăng ký tín điều TDD (điều này có thể chấp nhận được, nhưng bạn phải làm rõ!). Ngoài ra, chính xác thì xét nghiệm "tái sử dụng" là gì? Không phải các bài kiểm tra, theo định nghĩa, cụ thể cho mã họ kiểm tra?
Andres F.

1
@AresresF. Không, các bài kiểm tra không cụ thể đối với mã mà họ kiểm tra; chúng cụ thể đối với hành vi mà chúng kiểm tra. Vì vậy, giả sử bạn đã có một mô-đun Widget hoàn chỉnh với một bộ các bài kiểm tra xác minh rằng Widget đang hoạt động chính xác. Đồng nghiệp của bạn đến với BetterWidget, người có ý định làm điều tương tự như Widget nhưng nhanh hơn ba lần. Nếu các kiểm tra cho Widget được nhúng trong mã nguồn của Widget giống như cách Lập trình Văn học nhúng tài liệu vào mã nguồn, thì bạn không thể áp dụng tốt các thử nghiệm đó cho BetterWidget để xác minh rằng nó hoạt động giống như Widget.
Caleb

@AresresF. không cần chỉ định bạn không theo TDD. nó không phải là một mặc định vũ trụ. Đối với các điểm tái sử dụng. Khi kiểm tra một hệ thống, bạn quan tâm đến đầu vào và đầu ra, không phải bên trong. Sau đó, khi bạn cần tạo một hệ thống mới hoạt động giống như nó nhưng được triển khai theo cách khác, thật tuyệt khi có các thử nghiệm mà bạn có thể chạy trên cả hệ thống cũ và hệ thống mới. điều này đã xảy ra với tôi hơn một lần, đôi khi bạn cần phải làm việc trên hệ thống mới trong khi cái cũ vẫn còn hoạt động hoặc thậm chí chạy chúng cạnh nhau. hãy nhìn vào cách Facebook đang thử nghiệm 'phản ứng sợi' với các thử nghiệm phản ứng để đạt được sự tương đương.
dùng1852503

10

Dưới đây là một số lý do bổ sung mà tôi có thể nghĩ ra:

  • có các thử nghiệm trong một thư viện riêng biệt giúp liên kết thư viện đó với khung thử nghiệm của bạn dễ dàng hơn và không phải mã sản xuất của bạn (điều này có thể tránh được bởi một số bộ xử lý trước, nhưng tại sao lại xây dựng một điều như vậy khi giải pháp dễ dàng hơn là viết các thử nghiệm trong một nơi riêng biệt)

  • các bài kiểm tra về một chức năng, một lớp, một thư viện thường được viết từ quan điểm "người dùng" (người dùng của chức năng / lớp / thư viện đó). "Sử dụng mã" như vậy thường được viết trong một tệp hoặc thư viện riêng biệt và một bài kiểm tra có thể rõ ràng hơn hoặc "thực tế hơn" nếu nó bắt chước tình huống đó.


5

Nếu các kiểm tra là nội tuyến, cần phải xóa mã bạn cần kiểm tra khi bạn giao sản phẩm cho khách hàng của mình. Vì vậy, một nơi bổ sung nơi bạn lưu trữ các bài kiểm tra của mình chỉ đơn giản là phân tách giữa mã bạn cần và mã khách hàng của bạn cần.


9
Không phải là không thể. Nó sẽ yêu cầu một giai đoạn tiền xử lý bổ sung, giống như LP. Nó có thể được thực hiện dễ dàng bằng C, hoặc một ngôn ngữ biên dịch-js chẳng hạn.
Chris Devereux

+1 để chỉ ra điều đó cho tôi. Tôi đã chỉnh sửa câu trả lời của mình để thể hiện điều đó.
mhr

Cũng có một giả định rằng kích thước mã quan trọng trong mọi trường hợp. Chỉ vì nó quan trọng trong một số trường hợp không có nghĩa là nó quan trọng trong mọi trường hợp. Có rất nhiều môi trường nơi các lập trình viên không được điều khiển để tối ưu hóa kích thước mã nguồn. Nếu đó là trường hợp, họ sẽ không tạo ra nhiều lớp.
bảo vệ zumalifif

5

Ý tưởng này chỉ đơn giản là một phương pháp "Self_Test" trong bối cảnh thiết kế dựa trên đối tượng hoặc hướng đối tượng. Nếu sử dụng ngôn ngữ dựa trên đối tượng được biên dịch như Ada, tất cả mã tự kiểm tra sẽ được trình biên dịch đánh dấu là không sử dụng (không bao giờ được gọi) trong quá trình biên dịch sản xuất, và do đó tất cả sẽ được tối ưu hóa - không có mã nào xuất hiện trong kết quả thực thi.

Sử dụng phương pháp "Self_Test" là một ý tưởng cực kỳ tốt và nếu các lập trình viên thực sự quan tâm đến chất lượng thì tất cả họ sẽ thực hiện nó. Tuy nhiên, một vấn đề quan trọng là phương pháp "Self_Test" cần phải có kỷ luật nghiêm ngặt, trong đó nó không thể truy cập bất kỳ chi tiết triển khai nào và thay vào đó chỉ phải dựa vào tất cả các phương thức được công bố khác trong đặc tả của đối tượng. Rõ ràng, nếu tự kiểm tra thất bại, việc thực hiện sẽ cần phải thay đổi. Tự kiểm tra phải được kiểm tra nghiêm ngặt tất cả các thuộc tính được công bố của các phương thức của đối tượng, nhưng không bao giờ dựa vào bất kỳ cách nào dựa trên bất kỳ chi tiết nào của bất kỳ triển khai cụ thể nào.

Các ngôn ngữ dựa trên đối tượng và hướng đối tượng thường xuyên cung cấp chính xác loại kỷ luật đó đối với các phương thức bên ngoài đối tượng được kiểm tra (chúng thực thi đặc tả của đối tượng, ngăn chặn mọi truy cập vào chi tiết triển khai của nó và gây ra lỗi biên dịch nếu phát hiện bất kỳ nỗ lực nào như vậy ). Nhưng các phương thức bên trong của đối tượng đều được cấp quyền truy cập đầy đủ vào mọi chi tiết triển khai. Vì vậy, phương thức tự kiểm tra là trong một tình huống duy nhất: nó cần phải là một phương thức bên trong vì bản chất của nó (tự kiểm tra rõ ràng là một phương thức của đối tượng đang được kiểm tra), nhưng nó cần phải nhận tất cả các quy tắc biên dịch của một phương thức bên ngoài ( nó phải độc lập với các chi tiết thực hiện của đối tượng). Rất ít nếu có bất kỳ ngôn ngữ lập trình nào cung cấp khả năng kỷ luật một đối tượng ' Phương thức bên trong như thể nó là một phương thức bên ngoài. Vì vậy, đây là một vấn đề thiết kế ngôn ngữ lập trình quan trọng.

Trong trường hợp không có hỗ trợ ngôn ngữ lập trình phù hợp, cách tốt nhất để làm điều đó là tạo một đối tượng đồng hành. Nói cách khác, đối với mọi đối tượng bạn viết mã (hãy gọi nó là "Big_Object"), bạn cũng tạo một đối tượng đồng hành thứ hai có tên bao gồm một hậu tố chuẩn được nối với tên của đối tượng "thực" (trong trường hợp này là "Big_Object_Self_Test ") và có thông số kỹ thuật bao gồm một phương thức duy nhất (" Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) return Boolean; "). Đối tượng đồng hành sau đó sẽ phụ thuộc vào đặc tả của đối tượng chính và trình biên dịch sẽ thực thi đầy đủ tất cả các quy tắc của đặc tả đó đối với việc triển khai của đối tượng đồng hành.


4

Điều này là để đáp ứng với một số lượng lớn ý kiến ​​cho rằng các thử nghiệm nội tuyến không được thực hiện vì khó có thể loại bỏ mã thử nghiệm khỏi các bản dựng phát hành. Điều này là sai sự thật. Hầu như tất cả các trình biên dịch và trình biên dịch đều hỗ trợ điều này, với các ngôn ngữ được biên dịch, chẳng hạn như C, C ++, C #, điều này được thực hiện với những gì được gọi là chỉ thị của trình biên dịch.

Trong trường hợp của c # (tôi cũng tin c ++, cú pháp có thể hơi khác tùy thuộc vào trình biên dịch bạn đang sử dụng) đây là cách bạn có thể làm điều đó.

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif

Bởi vì điều này sử dụng các chỉ thị của trình biên dịch, mã sẽ không tồn tại trong các tệp thực thi được xây dựng nếu các cờ không được đặt. Đây cũng là cách bạn thực hiện các chương trình "viết một lần, biên dịch hai lần" cho nhiều nền tảng / phần cứng.


2

Chúng tôi sử dụng các bài kiểm tra nội tuyến với mã Perl của chúng tôi. Có một mô-đun, Test :: Inline , tạo các tệp thử nghiệm từ mã nội tuyến.

Tôi không đặc biệt giỏi trong việc tổ chức các bài kiểm tra của mình và đã tìm thấy chúng dễ dàng hơn và có nhiều khả năng được duy trì khi nội tuyến.

Đáp lại một vài lo ngại được nêu ra:

  • Các bài kiểm tra nội tuyến được viết trong các phần POD, vì vậy chúng không phải là một phần của mã thực tế. Họ bị bỏ qua bởi trình thông dịch, vì vậy không có sự phình to mã.
  • Chúng tôi sử dụng Vim gấp để ẩn các phần kiểm tra. Điều duy nhất bạn thấy là một dòng duy nhất ở trên mỗi phương thức đang được thử nghiệm +-- 33 lines: #test----. Khi bạn muốn làm việc với bài kiểm tra, bạn chỉ cần mở rộng nó.
  • Mô-đun Test :: Inline "biên dịch" các thử nghiệm thành các tệp tương thích TAP thông thường, để chúng có thể cùng tồn tại với các thử nghiệm truyền thống.

Để tham khảo:


1

Erlang 2 thực sự hỗ trợ kiểm tra nội tuyến. Bất kỳ biểu thức boolean nào trong mã không được sử dụng (ví dụ được gán cho một biến hoặc được thông qua) sẽ tự động được coi là một thử nghiệm và được trình biên dịch đánh giá; nếu biểu thức là sai, mã không biên dịch.


1

Một lý do khác để phân tách các bài kiểm tra là bạn thường sử dụng các thư viện bổ sung hoặc thậm chí khác nhau để kiểm tra hơn là để thực hiện thực tế. Nếu bạn kết hợp các kiểm tra và thực hiện, việc sử dụng chính xác các thư viện kiểm thử trong quá trình thực hiện có thể bị trình biên dịch bắt gặp.

Ngoài ra, các thử nghiệm có xu hướng có nhiều dòng mã hơn các phần triển khai mà chúng kiểm tra, vì vậy bạn sẽ gặp khó khăn khi tìm cách triển khai giữa tất cả các thử nghiệm. :-)


0

Điều này không đúng. Sẽ tốt hơn nhiều nếu đặt các bài kiểm tra đơn vị của bạn dọc theo mã sản xuất khi mã sản xuất đặc biệt là khi quy trình sản xuất thuần túy.

Nếu bạn đang phát triển theo .NET chẳng hạn, bạn có thể đặt mã kiểm tra của mình vào tổ hợp sản xuất, sau đó sử dụng Scalpel để xóa chúng trước khi gửi.

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.