Mã trùng lặp có dễ dung nạp hơn trong các bài kiểm tra đơn vị không?


113

Tôi đã làm hỏng một số bài kiểm tra đơn vị một thời gian trước khi tôi xem xét và cấu trúc lại chúng để làm cho chúng KHÔ hơn - ý định của mỗi bài kiểm tra không còn rõ ràng nữa. Có vẻ như có sự đánh đổi giữa khả năng đọc và khả năng bảo trì của các bài kiểm tra. Nếu tôi để mã trùng lặp trong các bài kiểm tra đơn vị, chúng sẽ dễ đọc hơn, nhưng sau đó nếu tôi thay đổi SUT , tôi sẽ phải theo dõi và thay đổi từng bản sao của mã trùng lặp.

Bạn có đồng ý rằng sự đánh đổi này tồn tại không? Nếu vậy, bạn thích các bài kiểm tra của mình có thể đọc được hay có thể bảo trì?

Câu trả lời:


68

Mã trùng lặp là một mùi trong mã thử nghiệm đơn vị cũng giống như trong mã khác. Nếu bạn có mã trùng lặp trong các thử nghiệm, thì việc cấu trúc lại mã triển khai sẽ khó hơn vì bạn có số lượng thử nghiệm không tương xứng để cập nhật. Các bài kiểm tra sẽ giúp bạn tự tin cấu trúc lại, thay vì trở thành một gánh nặng lớn cản trở công việc của bạn trên mã đang được kiểm tra.

Nếu việc sao chép được trong vật cố thiết lập, hãy xem xét việc sử dụng nhiều setUpphương pháp hoặc cung cấp thêm (hoặc nhiều linh hoạt) Phương pháp sáng tạo .

Nếu sự trùng lặp nằm trong mã thao tác với SUT, thì hãy tự hỏi tại sao nhiều thử nghiệm được gọi là "đơn vị" đang thực hiện cùng một chức năng.

Nếu có sự trùng lặp trong các xác nhận, thì có lẽ bạn cần một số Xác nhận tùy chỉnh . Ví dụ: nếu nhiều thử nghiệm có chuỗi xác nhận như:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

Sau đó, có lẽ bạn cần một assertPersonEqualphương pháp duy nhất để bạn có thể viết assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (Hoặc có lẽ bạn chỉ cần nạp chồng toán tử bình đẳng vào Person.)

Như bạn đã đề cập, điều quan trọng là mã kiểm tra phải có thể đọc được. Đặc biệt, điều quan trọng là mục đích của một bài kiểm tra phải rõ ràng. Tôi thấy rằng nếu nhiều bài kiểm tra hầu như giống nhau, (ví dụ: 3/4 số dòng giống nhau hoặc gần như giống nhau) thì khó có thể phát hiện và nhận ra sự khác biệt đáng kể nếu không đọc kỹ và so sánh chúng. Vì vậy, tôi thấy rằng việc tái cấu trúc để loại bỏ sự trùng lặp sẽ giúp dễ đọc hơn, bởi vì mọi dòng của mọi phương pháp thử nghiệm đều liên quan trực tiếp đến mục đích của thử nghiệm. Điều đó hữu ích hơn nhiều cho người đọc hơn là sự kết hợp ngẫu nhiên giữa các dòng có liên quan trực tiếp và các dòng chỉ là bản ghi sẵn.

Điều đó nói rằng, đôi khi các bài kiểm tra đang thực hiện các tình huống phức tạp giống nhau nhưng vẫn khác biệt đáng kể và thật khó để tìm ra cách tốt để giảm sự trùng lặp. Sử dụng cách hiểu thông thường: nếu bạn cảm thấy các bài kiểm tra có thể đọc được và làm rõ ý định của chúng và bạn cảm thấy thoải mái với việc có thể cần cập nhật nhiều hơn số lượng bài kiểm tra tối thiểu về mặt lý thuyết khi cấu trúc lại mã được gọi bởi các bài kiểm tra, thì hãy chấp nhận sự không hoàn hảo và di chuyển vào thứ gì đó hiệu quả hơn. Bạn luôn có thể quay lại và cấu trúc lại các bài kiểm tra sau, khi cảm hứng xuất hiện!


30
"Mã trùng lặp là một mùi trong mã thử nghiệm đơn vị cũng giống như trong mã khác." Không. "Nếu bạn có mã trùng lặp trong các thử nghiệm, thì việc cấu trúc lại mã triển khai sẽ khó hơn vì bạn có số lượng thử nghiệm không tương xứng để cập nhật." Điều này xảy ra vì bạn đang thử nghiệm API riêng thay vì API công khai.

15
Nhưng để ngăn mã trùng lặp trong các bài kiểm tra đơn vị, bạn thường cần đưa ra logic mới. Tôi không nghĩ rằng các bài kiểm tra đơn vị nên chứa logic vì khi đó bạn sẽ cần các bài kiểm tra đơn vị của các bài kiểm tra đơn vị.
Petr Peller

@ user11617 vui lòng xác định "API riêng tư" và "Api công khai". Theo hiểu biết của tôi, Api công khai là Api hiển thị cho người tiêu dùng thế giới bên ngoài / bên thứ 3 và được lập phiên bản rõ ràng qua SemVer hoặc tương tự, bất kỳ thứ gì khác là riêng tư. Với định nghĩa này, hầu như tất cả các bài kiểm tra Đơn vị đều đang kiểm tra "API riêng" và do đó nhạy cảm hơn với sự trùng lặp mã, điều này tôi nghĩ là đúng.
KolA

@KolA "Công khai" không có nghĩa là người tiêu dùng bên thứ ba - đây không phải là một API web. API công khai của một lớp đề cập đến các phương thức được sử dụng bởi mã máy khách (thường không / không nên thay đổi nhiều) - thường là các phương thức "công khai". API riêng đề cập đến logic và phương pháp được sử dụng nội bộ. Chúng không nên được truy cập từ bên ngoài lớp học. Đây là một lý do tại sao điều quan trọng là phải đóng gói logic một cách chính xác trong một lớp bằng cách sử dụng các công cụ sửa đổi truy cập hoặc quy ước trong ngôn ngữ đang được sử dụng.
Nathan

@Nathan bất kỳ gói thư viện / dll / nuget nào có người tiêu dùng bên thứ ba, nó không cần phải là một ứng dụng web. Những gì tôi đề cập là rất phổ biến khi khai báo các lớp công khai và thành viên không được người tiêu dùng thư viện sử dụng trực tiếp (hoặc tốt nhất là đặt chúng nội bộ và chú thích hợp ngữ với InternalsVibleToAttribute) chỉ để cho phép các bài kiểm tra đơn vị tiếp cận trực tiếp với chúng. Nó dẫn đến đống bài kiểm tra cùng witht thực hiện và làm cho họ thêm một gánh nặng hơn lợi thế
Kola

186

Khả năng đọc quan trọng hơn đối với các bài kiểm tra. Nếu một bài kiểm tra không thành công, bạn muốn vấn đề trở nên rõ ràng. Nhà phát triển không cần phải lướt qua rất nhiều mã kiểm tra có yếu tố nặng nề để xác định chính xác điều gì không thành công. Bạn không muốn mã kiểm tra của mình trở nên phức tạp đến mức bạn cần phải viết các bài kiểm tra đơn vị.

Tuy nhiên, loại bỏ trùng lặp thường là một điều tốt, miễn là nó không che khuất bất cứ điều gì và việc loại bỏ trùng lặp trong các thử nghiệm của bạn có thể dẫn đến một API tốt hơn. Chỉ cần đảm bảo rằng bạn không vượt qua điểm giảm dần lợi nhuận.


xUnit và những người khác chứa đối số 'thông báo' trong các lệnh gọi khẳng định. Ý tưởng hay là đặt các cụm từ có nghĩa để cho phép các nhà phát triển nhanh chóng tìm ra kết quả thử nghiệm thất bại.
seand

1
@seand Bạn có thể cố gắng giải thích những gì xác nhận của bạn đang kiểm tra, nhưng khi nó không thành công và chứa mã bị che khuất thì nhà phát triển vẫn cần phải đi và giải nén nó. IMO Điều quan trọng hơn là có mã để tự mô tả ở đó.
IgorK

1
@Kristopher ,? Tại sao điều này được đăng là wiki cộng đồng?
Pacerier

@Pacerier Tôi không biết. Đã từng có những quy tắc phức tạp về những thứ tự động trở thành wiki cộng đồng.
Kristopher Johnson

Đối với khả năng đọc của báo cáo quan trọng hơn kiểm tra, đặc biệt khi thực hiện tích hợp hoặc kiểm tra kết thúc từ đầu đến cuối, các tình huống có thể đủ phức tạp để tránh điều hướng một chút, có thể tìm thấy lỗi nhưng một lần nữa đối với tôi lỗi trong báo cáo nên giải thích vấn đề đủ tốt.
Anirudh

47

Mã thực hiện và các thử nghiệm là các loài động vật khác nhau và các quy tắc bao thanh toán áp dụng khác nhau cho chúng.

Mã hoặc cấu trúc trùng lặp luôn là một vấn đề trong mã triển khai. Khi bạn bắt đầu thực hiện chương trình soạn sẵn, bạn cần phải sửa đổi các phần tóm tắt của mình.

Mặt khác, mã thử nghiệm phải duy trì mức độ trùng lặp. Nhân bản trong mã thử nghiệm đạt được hai mục tiêu:

  • Giữ các bài kiểm tra được tách biệt. Việc ghép nối thử nghiệm quá mức có thể gây khó khăn cho việc thay đổi một thử nghiệm không đạt cần cập nhật vì hợp đồng đã thay đổi.
  • Giữ các bài kiểm tra có ý nghĩa riêng biệt. Khi một thử nghiệm không thành công, cần phải thẳng thắn một cách hợp lý để tìm ra chính xác nó đang thử nghiệm cái gì.

Tôi có xu hướng bỏ qua sự trùng lặp nhỏ trong mã thử nghiệm miễn là mỗi phương pháp thử nghiệm vẫn ngắn hơn khoảng 20 dòng. Tôi thích khi nhịp độ thiết lập-chạy-xác minh rõ ràng trong các phương pháp thử nghiệm.

Khi trùng lặp tăng lên trong phần "xác minh" của các thử nghiệm, việc xác định các phương pháp xác nhận tùy chỉnh thường có lợi. Tất nhiên, các phương thức đó vẫn phải kiểm tra một quan hệ được xác định rõ ràng có thể được thể hiện rõ ràng trong tên phương thức: assertPegFitsInHole-> tốt, assertPegIsGood-> xấu.

Khi các phương pháp thử nghiệm phát triển dài và lặp đi lặp lại, đôi khi tôi thấy hữu ích khi xác định các mẫu thử nghiệm điền vào chỗ trống có một vài tham số. Sau đó, các phương pháp thử nghiệm thực tế được rút gọn thành lời gọi phương thức mẫu với các tham số thích hợp.

Đối với rất nhiều thứ trong lập trình và thử nghiệm, không có câu trả lời rõ ràng. Bạn cần phát triển thị hiếu, và cách tốt nhất để làm như vậy là phạm sai lầm.


8

Tôi đồng ý. Sự đánh đổi tồn tại nhưng khác nhau ở những nơi khác nhau.

Nhiều khả năng tôi sẽ cấu trúc lại mã trùng lặp để thiết lập trạng thái. Nhưng ít có khả năng cấu trúc lại phần kiểm tra thực sự sử dụng mã. Điều đó nói rằng, nếu việc thực thi mã luôn mất vài dòng mã thì tôi có thể nghĩ rằng đó là mùi và cấu trúc lại mã thực đang được thử nghiệm. Và điều đó sẽ cải thiện khả năng đọc và khả năng bảo trì của cả mã và các bài kiểm tra.


Tôi nghĩ rằng đây là một ý tưởng tốt. Nếu bạn có nhiều trùng lặp, hãy xem liệu bạn có thể cấu trúc lại để tạo một "bộ cố định thử nghiệm" chung mà theo đó nhiều thử nghiệm có thể chạy hay không. Điều này sẽ loại bỏ mã thiết lập / xé nhỏ trùng lặp.
Outlaw Programmer,

8

Bạn có thể giảm sự lặp lại bằng cách sử dụng một số hương vị khác nhau của các phương pháp tiện ích thử nghiệm .

Tôi chấp nhận được việc lặp lại trong mã thử nghiệm hơn là trong mã sản xuất, nhưng đôi khi tôi cảm thấy thất vọng vì nó. Khi bạn thay đổi thiết kế của một lớp và bạn phải quay lại và chỉnh sửa 10 phương pháp kiểm tra khác nhau mà tất cả đều thực hiện các bước thiết lập giống nhau, điều đó thật khó chịu.


6

Jay Fields đã đặt ra cụm từ "DSL phải là DAMP, không phải KHÔ", trong đó DAMP có nghĩa là các cụm từ mô tả và có ý nghĩa . Tôi nghĩ điều tương tự cũng áp dụng cho các bài kiểm tra. Rõ ràng, trùng lặp quá nhiều là không tốt. Nhưng loại bỏ sự trùng lặp bằng mọi giá còn tệ hơn. Kiểm tra phải hoạt động như thông số kỹ thuật tiết lộ ý định. Ví dụ: nếu bạn chỉ định cùng một đối tượng địa lý từ nhiều góc độ khác nhau, thì dự kiến ​​sẽ có một lượng trùng lặp nhất định.


3

TÔI YÊU rspec vì điều này:

Nó có 2 điều để giúp -

  • các nhóm ví dụ được chia sẻ để kiểm tra hành vi phổ biến.
    bạn có thể xác định một tập hợp các thử nghiệm, sau đó 'bao gồm' tập hợp đó trong các thử nghiệm thực của bạn.

  • các bối cảnh lồng nhau.
    về cơ bản bạn có thể có một phương thức 'thiết lập' và 'chia nhỏ' cho một tập hợp con cụ thể của các bài kiểm tra của bạn, không chỉ cho mỗi bài kiểm tra trong lớp.

.NET / Java / các khuôn khổ thử nghiệm khác áp dụng các phương pháp này càng sớm thì càng tốt (hoặc bạn có thể sử dụng IronRuby hoặc JRuby để viết các thử nghiệm của mình, mà cá nhân tôi nghĩ là lựa chọn tốt hơn)


3

Tôi cảm thấy rằng mã thử nghiệm yêu cầu một mức độ kỹ thuật tương tự thường được áp dụng cho mã sản xuất. Chắc chắn có thể có những lập luận ủng hộ tính dễ đọc và tôi đồng ý rằng điều đó quan trọng.

Tuy nhiên, theo kinh nghiệm của tôi, tôi thấy rằng các bài kiểm tra được kiểm chứng tốt sẽ dễ đọc và dễ hiểu hơn. Nếu có 5 bài kiểm tra mà mỗi bài trông giống nhau ngoại trừ một biến đã được thay đổi và xác nhận ở cuối, có thể rất khó để tìm ra mục khác biệt duy nhất đó là gì. Tương tự, nếu nó được tính theo yếu tố để chỉ có thể nhìn thấy biến đang thay đổi và xác nhận, thì thật dễ dàng để tìm ra thử nghiệm đang làm gì ngay lập tức.

Việc tìm kiếm mức độ trừu tượng phù hợp khi thử nghiệm có thể khó khăn và tôi cảm thấy điều đó là đáng làm.


2

Tôi không nghĩ rằng có mối quan hệ giữa mã trùng lặp và dễ đọc hơn. Tôi nghĩ mã thử nghiệm của bạn phải tốt như mã khác của bạn. Mã không lặp lại dễ đọc hơn sau đó mã trùng lặp khi được thực hiện tốt.


2

Tốt nhất, các bài kiểm tra đơn vị sẽ không thay đổi nhiều sau khi chúng được viết nên tôi sẽ nghiêng về khả năng đọc.

Việc có các bài kiểm tra đơn vị càng rời rạc càng tốt cũng giúp giữ cho các bài kiểm tra tập trung vào chức năng cụ thể mà chúng đang nhắm mục tiêu.

Như đã nói, tôi có xu hướng thử và sử dụng lại một số đoạn mã nhất định mà tôi sử dụng lặp đi lặp lại, chẳng hạn như mã thiết lập giống hệt nhau trong một tập hợp các bài kiểm tra.


2

"đã cấu trúc lại chúng để làm cho chúng KHÔ hơn - mục đích của mỗi bài kiểm tra không còn rõ ràng nữa"

Có vẻ như bạn đã gặp sự cố khi tái cấu trúc. Tôi chỉ đoán, nhưng nếu nó trở nên kém rõ ràng hơn, điều đó không có nghĩa là bạn vẫn còn nhiều việc phải làm để bạn có những bài kiểm tra thanh lịch hợp lý mà hoàn toàn rõ ràng?

Đó là lý do tại sao các bài kiểm tra là một lớp con của UnitTest - vì vậy bạn có thể thiết kế các bộ kiểm tra tốt, chính xác, dễ xác thực và rõ ràng.

Vào thời xa xưa, chúng tôi có các công cụ kiểm tra sử dụng các ngôn ngữ lập trình khác nhau. Thật khó (hoặc không thể) để thiết kế các bài kiểm tra dễ chịu, dễ làm việc.

Bạn có toàn quyền - bất kỳ ngôn ngữ nào bạn đang sử dụng - Python, Java, C # - vì vậy hãy sử dụng tốt ngôn ngữ đó. Bạn có thể đạt được mã kiểm tra đẹp, rõ ràng và không quá dư thừa. Không có sự đánh đổ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.