Làm cho một phương thức riêng tư công khai để kiểm tra đơn vị nó ý tưởng tốt?


301

Người điều hành Lưu ý: Hiện đã có 39 câu trả lời được đăng ở đây (một số đã bị xóa). Trước khi bạn đăng câu trả lời của mình , hãy xem xét liệu bạn có thể thêm điều gì đó có ý nghĩa vào cuộc thảo luận hay không. Bạn có nhiều khả năng chỉ lặp lại những gì người khác đã nói.


Thỉnh thoảng tôi thấy mình cần phải tạo một phương thức riêng tư trong lớp công khai chỉ để viết một số bài kiểm tra đơn vị cho nó.

Thông thường điều này là do phương thức chứa logic được chia sẻ giữa các phương thức khác trong lớp và việc kiểm tra logic một cách gọn gàng hơn hoặc lý do khác có thể là tôi muốn kiểm tra logic được sử dụng trong các luồng đồng bộ mà không phải lo lắng về các vấn đề luồng. .

Có phải những người khác thấy mình làm điều này, bởi vì tôi không thực sự thích làm điều đó ?? Cá nhân tôi nghĩ rằng tiền thưởng vượt xa các vấn đề về việc công khai phương thức mà không thực sự cung cấp bất kỳ dịch vụ nào ngoài lớp ...

CẬP NHẬT

Cảm ơn câu trả lời của mọi người, dường như đã khơi gợi sự quan tâm của mọi người. Tôi nghĩ rằng sự đồng thuận chung là thử nghiệm nên xảy ra thông qua API công khai vì đây là cách duy nhất một lớp sẽ được sử dụng và tôi đồng ý với điều này. Một vài trường hợp tôi đã đề cập ở trên, nơi tôi sẽ làm điều này ở trên là những trường hợp không phổ biến và tôi nghĩ rằng lợi ích của việc làm nó là xứng đáng.

Tuy nhiên, tôi có thể thấy mọi người đều cho rằng điều đó không bao giờ thực sự xảy ra. Và khi nghĩ về nó nhiều hơn một chút, tôi nghĩ việc thay đổi mã của bạn để phù hợp với các bài kiểm tra là một ý tưởng tồi - sau tất cả, tôi cho rằng kiểm thử là một công cụ hỗ trợ theo cách và thay đổi một hệ thống thành 'công cụ hỗ trợ' nếu bạn muốn, là trắng trợn thực hành xấu.


2
"Thay đổi một hệ thống thành 'hỗ trợ một công cụ hỗ trợ' nếu bạn muốn, là hành vi xấu trắng trợn". Vâng, vâng, loại. Nhưng OTOH nếu hệ thống của bạn không hoạt động tốt với các công cụ thiết lập, có lẽ cái gì sai với hệ thống.
Thilo

1
Làm thế nào đây không phải là bản sao của stackoverflow.com/questions/105007/do-you-test-private-method ?
Sanghyun Lee

1
Không phải là một câu trả lời, nhưng bạn luôn có thể truy cập chúng bằng phản xạ.
Zano

33
Không, bạn không nên phơi bày chúng. Thay vào đó, bạn nên kiểm tra xem lớp của bạn có hoạt động như bình thường không, thông qua API công khai. Và nếu phơi bày nội bộ thực sự là lựa chọn duy nhất (mà tôi nghi ngờ), thì ít nhất bạn nên bảo vệ gói người truy cập, để chỉ các lớp trong cùng một gói (như bài kiểm tra của bạn) có thể truy cập chúng.
JB Nizet

4
Vâng, đúng vậy. Hoàn toàn có thể chấp nhận được khi cung cấp khả năng hiển thị gói riêng tư (mặc định) để làm cho nó có thể truy cập được từ các thử nghiệm đơn vị. Các thư viện như Guava thậm chí còn cung cấp một @VisibileForTestingchú thích để thực hiện các phương pháp như vậy - Tôi khuyên bạn nên làm điều này để lý do cho phương pháp không được privateghi lại đúng cách.
Boris the Spider

Câu trả lời:


186

Lưu ý:
Câu trả lời này ban đầu được đăng cho câu hỏi Thử nghiệm đơn vị có bao giờ là lý do chính đáng để phơi bày các biến cá thể thông qua getters không? đã được hợp nhất vào cái này, vì vậy nó có thể là một chút cụ thể cho usecase được trình bày ở đó.

Như một tuyên bố chung, tôi thường tất cả để tái cấu trúc mã "sản xuất" để dễ kiểm tra hơn. Tuy nhiên, tôi không nghĩ rằng đó sẽ là một cuộc gọi tốt ở đây. Một bài kiểm tra đơn vị tốt (thường) không nên quan tâm đến chi tiết triển khai của lớp, chỉ về hành vi có thể nhìn thấy của nó. Thay vì để lộ các ngăn xếp bên trong cho bài kiểm tra, bạn có thể kiểm tra xem lớp trả về các trang theo thứ tự mà bạn mong đợi sau khi gọi first()hoặc last().

Ví dụ, hãy xem xét mã giả này:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}

8
Tôi hoàn toàn đồng ý với câu trả lời của bạn. Nhiều nhà phát triển sẽ bị cám dỗ sử dụng công cụ sửa đổi truy cập mặc định và biện minh rằng các khung nguồn mở tốt sử dụng chiến lược này vì vậy nó phải chính xác. Chỉ vì bạn không thể có nghĩa là bạn nên. +1
CKing

6
Mặc dù những người khác đã cung cấp những hiểu biết hữu ích, tôi phải nói rằng đây là giải pháp mà tôi thấy phù hợp nhất với vấn đề này. Nó giữ cho các bài kiểm tra hoàn toàn không tin vào cách thức hành vi dự định đạt được trong nội bộ. +10!
Gitahi Ng'ang'a

Nhân tiện, có ổn không khi trong ví dụ được cung cấp bởi @Mureinik ở đây, các phương thức thử nghiệm cuối cùng bao phủ nhiều hơn phương pháp thực tế đang thử nghiệm? Chẳng hạn, testFirst () gọi next (), đây không thực sự là phương thức được thử nghiệm ở đây, First () là. Sẽ không sạch sẽ và rõ ràng hơn khi chỉ có 1 phương thức kiểm tra bao gồm cả 4 phương thức điều hướng?
Gitahi Ng'ang'a

1
Mặc dù đây là lý tưởng, nhưng có những lúc bạn cần kiểm tra một thành phần đã làm điều gì đó đúng cách không chỉ là nó đã thành công. Điều này có thể khó hơn để kiểm tra hộp đen. Đôi khi bạn phải kiểm tra hộp trắng một thành phần.
Peter Lawrey

1
Tạo getters chỉ vì mục đích kiểm tra đơn vị? Điều này chắc chắn đi ngược lại với suy nghĩ của OOP / Object trong đó giao diện sẽ phơi bày hành vi chứ không phải dữ liệu.
IntelliData

151

Cá nhân, tôi thà kiểm tra đơn vị sử dụng API công cộng và tôi muốn chắc chắn không bao giờ làm cho phương pháp nào riêng chỉ để làm cho nó dễ dàng để kiểm tra.

Nếu bạn thực sự muốn kiểm tra riêng phương thức riêng lẻ, trong Java, bạn có thể sử dụng Easymock / Powermock để cho phép bạn làm điều này.

Bạn phải thực dụng về nó và bạn cũng nên nhận thức được lý do tại sao mọi thứ khó kiểm tra.

' Nghe các bài kiểm tra ' - nếu khó kiểm tra, đó có phải là cho bạn biết điều gì về thiết kế của bạn không? Bạn có thể tái cấu trúc đến nơi mà một thử nghiệm cho phương pháp này sẽ không quan trọng và dễ dàng được bao phủ bởi thử nghiệm thông qua api công cộng?

Đây là những gì Michael Feathers đã nói trong ' Làm việc hiệu quả với Bộ luật kế thừa "

"Nhiều người dành nhiều thời gian để thử tìm hiểu cách khắc phục vấn đề này ... câu trả lời thực sự là nếu bạn có nhu cầu thử nghiệm một phương thức riêng tư, thì phương pháp không nên riêng tư; làm phiền bạn, rất có thể, đó là bởi vì nó là một phần của trách nhiệm riêng biệt, nó nên ở một lớp khác. " [ Làm việc hiệu quả với Bộ luật kế thừa (2005) của M. Feathers]


5
+1 cho Easymock / Powermock và không bao giờ công khai phương thức riêng tư
Leonard Brünings

51
+1 Đối với "Nghe các bài kiểm tra". Nếu bạn cảm thấy cần phải thử nghiệm một phương thức riêng tư, rất có thể logic là logic trong phương thức đó phức tạp đến mức nó phải nằm trong một lớp trợ giúp riêng. Sau đó, bạn có thể kiểm tra lớp người trợ giúp.
Phil

2
"Nghe các bài kiểm tra" dường như không hoạt động. Đây là một liên kết được lưu trong bộ nhớ cache: webcache.googleusercontent.com/ Kẻ
Jess Telford

1
Tôi đồng ý với @Phil (đặc biệt là tài liệu tham khảo), nhưng tôi thường thấy mình trong tình huống gà / trứng với mã kế thừa. Tôi biết phương pháp này thuộc về một lớp khác, tuy nhiên tôi không thoải mái 100% khi tái cấu trúc mà không cần kiểm tra ngay từ đầu.
Kevin

Em đồng ý bác sĩ. Nếu bạn cần kiểm tra một phương thức riêng thì có lẽ nó nên ở một lớp khác. Bạn có thể lưu ý rằng lớp / người trợ giúp khác mà phương thức riêng tư có khả năng trở thành công khai / được bảo vệ.
rupweb

67

Như những người khác đã nói, có phần nghi ngờ là đơn vị thử nghiệm các phương pháp riêng tư; đơn vị kiểm tra giao diện công cộng, không phải chi tiết thực hiện riêng tư.

Điều đó nói rằng, kỹ thuật tôi sử dụng khi tôi muốn kiểm tra đơn vị một cái gì đó riêng tư trong C # là hạ cấp bảo vệ khả năng truy cập từ riêng tư sang nội bộ, sau đó đánh dấu tổ hợp thử nghiệm đơn vị là một tổ hợp bạn bè bằng cách sử dụng InternalsVisibleTo . Tập hợp thử nghiệm đơn vị sau đó sẽ được phép coi bên trong là công khai, nhưng bạn không phải lo lắng về việc vô tình thêm vào khu vực bề mặt công cộng của bạn.


Tôi cũng sử dụng kỹ thuật này, nhưng như là phương sách cuối cùng cho mã kế thừa thường. Nếu bạn cần kiểm tra mã riêng, bạn có một lớp bị thiếu / lớp đang kiểm tra đang làm quá nhiều. Trích xuất mã đã nói vào một lớp mới mà bạn có thể kiểm tra một cách cô lập. Thủ thuật như thế này nên được sử dụng hiếm khi.
Fingerlas

Nhưng trích xuất một lớp và chỉ kiểm tra điều đó có nghĩa là các kiểm tra của bạn mất kiến ​​thức rằng lớp ban đầu thực sự đang sử dụng mã được trích xuất. Làm thế nào để bạn đề nghị bạn lấp đầy khoảng trống này trong các bài kiểm tra của bạn? Và nếu câu trả lời là chỉ kiểm tra giao diện chung của lớp gốc theo cách khiến logic được trích xuất được sử dụng, thì tại sao lại phải trích xuất nó ở vị trí đầu tiên? (Tôi không có ý định đối đầu ở đây, btw - Tôi luôn tự hỏi làm thế nào mọi người cân bằng được sự đánh đổi đó.)
Mal Ross

@mal Tôi có một bài kiểm tra hợp tác. Nói cách khác, một giả để xác minh lớp mới đã được gọi chính xác. Xem câu trả lời của tôi dưới đây.
Fingerlas

Bạn có thể viết một lớp kiểm tra kế thừa từ lớp với logic bên trong mà bạn muốn kiểm tra và cung cấp một phương thức công khai để sử dụng để kiểm tra logic bên trong không?
sholsinger

1
@sholsinger: trong C #, một lớp công khai có thể không được kế thừa từ một lớp bên trong.
Eric Lippert

45

Rất nhiều câu trả lời đề nghị chỉ kiểm tra giao diện công cộng, nhưng IMHO điều này là không thực tế - nếu một phương pháp thực hiện 5 bước, bạn sẽ muốn kiểm tra riêng năm bước đó, không phải tất cả cùng nhau. Điều này yêu cầu thử nghiệm tất cả năm phương pháp, mà (ngoài việc thử nghiệm) có thể khác private.

Cách thông thường để kiểm tra các phương thức "riêng tư" là cung cấp cho mỗi lớp giao diện riêng và tạo các phương thức "riêng tư" public, nhưng không bao gồm chúng trong giao diện. Bằng cách này, chúng vẫn có thể được kiểm tra, nhưng chúng không làm hỏng giao diện.

Có, điều này sẽ dẫn đến kết quả tập tin và lớp.

Vâng, điều này không làm cho publicprivatechỉ định dự phòng.

Vâng, đây là một cơn đau ở mông.

Thật không may, đây là một trong nhiều sự hy sinh mà chúng tôi thực hiện để làm cho mã có thể kiểm tra được . Có lẽ một ngôn ngữ trong tương lai (hoặc thậm chí là phiên bản tương lai của C # / Java) sẽ có các tính năng để làm cho khả năng kiểm tra lớp và mô-đun thuận tiện hơn; nhưng trong khi đó, chúng ta phải nhảy qua những cái vòng.


Có một số người sẽ lập luận rằng mỗi bước đó nên là lớp riêng của nó , nhưng tôi không đồng ý - nếu tất cả chúng đều chia sẻ trạng thái, không có lý do gì để tạo năm lớp riêng biệt trong đó năm phương thức sẽ làm. Thậm chí tệ hơn, điều này dẫn đến kết quả tập tin và lớp. Thêm vào đó , nó lây nhiễm API công khai của mô-đun của bạn - tất cả các lớp đó nhất thiết phải là publicnếu bạn muốn kiểm tra chúng từ mô-đun khác (hoặc bao gồm mã kiểm tra trong cùng một mô-đun, có nghĩa là vận chuyển mã kiểm tra với sản phẩm của bạn) .


2
câu trả lời hay cho nửa đầu
cmcginty

7
+1: Câu trả lời tốt nhất cho tôi. Nếu một nhà sản xuất ô tô không kiểm tra một phần của chiếc xe vì lý do tương tự, tôi không chắc ai sẽ mua nó. Phần quan trọng của mã nên được kiểm tra ngay cả khi chúng ta nên thay đổi nó thành công khai.
Tae-Sung Shin

1
Câu trả lời rất tốt. Bạn có phiếu bầu ấm áp của tôi. Tôi đã thêm một câu trả lời. Nó có thể bạn quan tâm.
davidxxx

29

Một bài kiểm tra đơn vị nên kiểm tra hợp đồng công khai, cách duy nhất để một lớp có thể được sử dụng trong các phần khác của mã. Một phương thức riêng là chi tiết triển khai, bạn không nên kiểm tra nó, miễn là API công khai hoạt động chính xác, việc triển khai không thành vấn đề và có thể được thay đổi mà không cần thay đổi trong các trường hợp thử nghiệm.


+1 và câu trả lời thực sự: stackoverflow.com/questions/4603847/
Kẻ

Afair, Tình huống hiếm hoi tôi từng quyết định sử dụng tư nhân trong trường hợp thử nghiệm là khi tôi đang xác minh tính chính xác của đối tượng - nếu nó đã đóng tất cả các trình xử lý JNI. Rõ ràng, nó không được hiển thị dưới dạng API công khai, tuy nhiên, nếu điều đó không xảy ra, rò rỉ bộ nhớ sẽ xảy ra. Nhưng thật buồn cười, sau này tôi cũng đã loại bỏ nó sau khi tôi giới thiệu trình phát hiện rò rỉ bộ nhớ như là một phần của API công khai.
kan

11
Đây là logic thiếu sót. Điểm của bài kiểm tra đơn vị là bắt lỗi sớm hơn. Bằng cách không kiểm tra các phương thức riêng tư, bạn sẽ để lại những khoảng trống trong thử nghiệm mà có thể không được phát hiện cho đến sau này. Trong trường hợp phương thức riêng phức tạp và không dễ dàng thực hiện bởi giao diện công cộng, thì nên thử nghiệm đơn vị.
cmcginty

6
@Casey: nếu phương thức riêng tư không được thực hiện bởi giao diện công cộng thì tại sao nó lại ở đó?
Thilo

2
@DaSilva_Ireland: Sẽ rất khó để duy trì các trường hợp thử nghiệm trong tương lai. Trường hợp sử dụng lớp thực sự duy nhất là thông qua phương thức công khai. Tức là tất cả các mã khác chỉ có thể sử dụng lớp thông qua API công khai. Vì vậy, nếu bạn muốn thay đổi triển khai và trường hợp thử nghiệm phương thức riêng tư không thành công, điều đó không có nghĩa là bạn đang làm gì đó sai, hơn nữa, nếu bạn chỉ kiểm tra riêng tư, nhưng không kiểm tra công khai đầy đủ, nó sẽ không bao gồm một số lỗi tiềm ẩn . Lý tưởng nhất là một trường hợp thử nghiệm nên bao gồm tất cả các trường hợp có thể và chỉ các trường hợp sử dụng thực tế của lớp.
kan

22

IMO, bạn nên viết các bài kiểm tra của mình không đưa ra các giả định sâu sắc về cách lớp của bạn thực hiện bên trong. Bạn có thể muốn cấu trúc lại nó sau bằng cách sử dụng một mô hình nội bộ khác nhưng vẫn đảm bảo tương tự mà việc thực hiện trước đó mang lại.

Hãy ghi nhớ điều đó, tôi khuyên bạn nên tập trung vào việc kiểm tra rằng hợp đồng của bạn vẫn được duy trì cho dù hiện tại lớp học của bạn có gì. Kiểm tra dựa trên tài sản của API công khai của bạn.


6
Tôi đồng ý rằng các bài kiểm tra đơn vị không cần phải viết lại sau khi tái cấu trúc mã là tốt hơn. Và không chỉ vì thời gian bạn sẽ dành để viết lại các bài kiểm tra đơn vị. Một lý do quan trọng hơn nhiều là tôi coi mục đích quan trọng nhất của các bài kiểm tra đơn vị là mã của bạn vẫn hoạt động chính xác sau khi tái cấu trúc. Nếu bạn phải viết lại tất cả các bài kiểm tra đơn vị của mình sau khi tái cấu trúc, thì bạn sẽ mất lợi ích đó của các bài kiểm tra đơn vị.
kasperd

20

Làm thế nào về việc làm cho nó gói riêng tư? Sau đó, mã kiểm tra của bạn có thể thấy nó (và các lớp khác trong gói của bạn), nhưng nó vẫn bị ẩn khỏi người dùng của bạn.

Nhưng thực sự, bạn không nên thử nghiệm các phương pháp riêng tư. Đó là những chi tiết thực hiện, và không phải là một phần của hợp đồng. Tất cả mọi thứ họ làm nên được bảo vệ bằng cách gọi các phương thức công khai (nếu họ có mã trong đó không được thực thi bởi các phương thức công khai, thì điều đó sẽ đi). Nếu mã riêng quá phức tạp, lớp có thể đang làm quá nhiều thứ và muốn tái cấu trúc.

Làm cho một phương pháp công khai là cam kết lớn. Khi bạn làm điều đó, mọi người sẽ có thể sử dụng nó và bạn không thể thay đổi chúng nữa.


+1 nếu bạn cần kiểm tra tư nhân của mình, có lẽ bạn đang thiếu một lớp
jk.

+1 cho lớp bị thiếu. Vui mừng khi thấy những người khác không nhảy vào băng nhóm "đánh dấu nội bộ" ngay lập tức.
Fingerlas

Chàng trai tôi đã phải đi một chặng đường dài để tìm câu trả lời riêng tư.
Bill K

17

Cập nhật : Tôi đã thêm một câu trả lời mở rộng hơn và đầy đủ hơn cho câu hỏi này ở nhiều nơi khác. Điều này có thể được tìm thấy trên blog của tôi .

Nếu tôi cần phải công khai một cái gì đó để kiểm tra nó, điều này thường gợi ý rằng hệ thống được thử nghiệm không tuân theo Nguyên tắc duy nhất có thể lặp lại . Do đó có một lớp bị thiếu nên được giới thiệu. Sau khi trích xuất mã vào một lớp mới, hãy đặt nó ở chế độ công khai. Bây giờ bạn có thể kiểm tra dễ dàng và bạn đang theo dõi SRP. Lớp khác của bạn chỉ đơn giản là phải gọi lớp mới này thông qua thành phần.

Làm cho các phương thức trở nên công khai / sử dụng các thủ thuật langauge như đánh dấu mã như có thể nhìn thấy để kiểm tra các giả định luôn luôn là biện pháp cuối cùng.

Ví dụ:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Tái cấu trúc điều này bằng cách giới thiệu một đối tượng trình xác nhận.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Bây giờ tất cả những gì chúng ta phải làm là kiểm tra xem trình xác nhận được gọi đúng. Quá trình xác nhận thực tế (logic riêng trước đây) có thể được kiểm tra trong sự cô lập thuần túy. Sẽ không cần phải thiết lập thử nghiệm phức tạp để đảm bảo xác thực này vượt qua.


1
Cá nhân tôi nghĩ rằng SRP có thể được đưa đi quá xa, ví dụ nếu tôi viết một dịch vụ tài khoản liên quan đến việc cập nhật / tạo / xóa tài khoản trong một ứng dụng, bạn có nói với tôi rằng bạn nên tạo một lớp riêng cho mỗi hoạt động không? Và ví dụ về việc xác nhận hợp lệ, nó có thực sự đáng để trích xuất logic xác thực sang một lớp khác khi nó sẽ không bao giờ được sử dụng ở bất kỳ nơi nào khác không? Imo chỉ làm cho mã ít đọc hơn, ít súc tích hơn và đóng gói ứng dụng của bạn với các lớp không cần thiết!
jcvandan

1
Tất cả phụ thuộc vào kịch bản. Định nghĩa của một người về "làm một việc" có thể khác với các bà mẹ. Trong trường hợp này cho chính bạn, rõ ràng mã riêng bạn muốn kiểm tra là phức tạp / một nỗi đau để thiết lập. Thực tế bạn muốn kiểm tra nó chứng tỏ nó đang làm quá nhiều. Do đó, có một đối tượng mới sẽ là lý tưởng.
Fingerlas

1
Đối với việc làm cho mã của bạn ít đọc hơn, tôi không đồng ý mạnh mẽ. Có một lớp để xác nhận dễ đọc hơn so với việc xác thực được nhúng trong đối tượng khác. Bạn vẫn có cùng số lượng mã, nó chỉ không được lồng trong một lớp lớn. Tôi muốn có một vài lớp học làm một việc rất tốt, hơn là một lớp lớn.
Fingerlas

4
@dormisher, liên quan đến dịch vụ tài khoản của bạn, hãy nghĩ về SRP như là một "lý do duy nhất để thay đổi." Nếu các hoạt động CRUD của bạn tự nhiên thay đổi cùng nhau, thì chúng sẽ phù hợp với SRP. Thông thường, bạn sẽ thay đổi những điều này vì lược đồ cơ sở dữ liệu có thể thay đổi, điều này sẽ tự nhiên ảnh hưởng đến việc chèn và cập nhật (mặc dù có lẽ không phải lúc nào cũng xóa).
Anthony Pegram

@finglas bạn nghĩ gì về điều này: stackoverflow.com/questions/29354729/ cấp . Tôi không cảm thấy muốn thử nghiệm phương thức riêng tư, vì vậy có lẽ tôi không nên tách nó thành một lớp, nhưng nó đang được sử dụng trong 2 phương thức công khai khác, vì vậy tôi sẽ kết thúc thử nghiệm hai lần cùng một logic.
Rodrigo Ruiz

13

Một số câu trả lời tuyệt vời. Một điều tôi không thấy được đề cập là với sự phát triển dựa trên thử nghiệm (TDD), các phương thức riêng được tạo ra trong giai đoạn tái cấu trúc (xem Phương pháp trích xuất để biết ví dụ về mẫu tái cấu trúc) và do đó phải có phạm vi kiểm tra cần thiết . Nếu được thực hiện chính xác (và tất nhiên, bạn sẽ nhận được một túi ý kiến ​​hỗn hợp khi nói đúng), bạn không cần phải lo lắng về việc phải công khai một phương thức riêng tư để bạn có thể kiểm tra nó.


11

Tại sao không tách thuật toán quản lý ngăn xếp thành một lớp tiện ích? Lớp tiện ích có thể quản lý các ngăn xếp và cung cấp các bộ truy cập công cộng. Kiểm tra đơn vị của nó có thể được tập trung vào chi tiết thực hiện. Các bài kiểm tra sâu cho các lớp khó về thuật toán rất hữu ích trong việc làm nhăn các trường hợp cạnh và đảm bảo phạm vi bảo hiểm.

Sau đó, lớp hiện tại của bạn có thể ủy thác sạch cho lớp tiện ích mà không để lộ bất kỳ chi tiết triển khai nào. Các thử nghiệm của nó sẽ liên quan đến yêu cầu phân trang như những người khác đã khuyến nghị.


10

Trong java, cũng có tùy chọn đặt gói riêng tư (nghĩa là bỏ công cụ sửa đổi hiển thị). Nếu các bài kiểm tra đơn vị của bạn nằm trong cùng một gói với lớp đang được kiểm tra thì nó có thể thấy các phương thức này và an toàn hơn một chút so với việc làm cho phương thức hoàn toàn công khai.


10

Các phương thức riêng thường được sử dụng làm phương pháp "người trợ giúp". Do đó, chúng chỉ trả về các giá trị cơ bản và không bao giờ hoạt động trên các trường hợp cụ thể của các đối tượng.

Bạn có một vài lựa chọn nếu bạn muốn kiểm tra chúng.

  • Sử dụng sự phản chiếu
  • Cấp quyền truy cập gói phương thức

Ngoài ra, bạn có thể tạo một lớp mới với phương thức trợ giúp như một phương thức công khai nếu đó là một ứng cử viên đủ tốt cho một lớp mới.

Có một bài viết rất tốt ở đây về điều này.


1
Nhận xét công bằng. Chỉ là một lựa chọn, có lẽ là một lựa chọn xấu.
adamjmarkham

@Finglas Tại sao kiểm tra mã riêng bằng cách sử dụng sự phản chiếu là một ý tưởng tồi?
Anshul

@Anshul phương thức riêng là chi tiết thực hiện và không phải hành vi. Nói cách khác, họ thay đổi. Tên thay đổi. Các thông số thay đổi. Nội dung thay đổi. Điều này có nghĩa là các bài kiểm tra này sẽ phá vỡ tất cả các thời gian. Mặt khác, các phương thức công khai ổn định hơn về mặt API nên có khả năng phục hồi tốt hơn. Cung cấp các bài kiểm tra của bạn về các phương pháp công cộng kiểm tra hành vi và không chi tiết thực hiện, bạn nên làm tốt.
Fingerlas

1
@Finglas Tại sao bạn không muốn kiểm tra chi tiết thực hiện? Đó là mã cần hoạt động và nếu nó thay đổi thường xuyên hơn, bạn hy vọng nó sẽ bị hỏng thường xuyên hơn, đó là lý do để kiểm tra nó nhiều hơn API công khai. Sau khi cơ sở mã của bạn ổn định, giả sử đôi khi trong tương lai ai đó sẽ xuất hiện và thay đổi một phương thức riêng phá vỡ cách thức lớp của bạn hoạt động bên trong. Một bài kiểm tra kiểm tra nội bộ của lớp bạn sẽ ngay lập tức cho họ biết rằng họ đã phá vỡ thứ gì đó. Vâng, nó sẽ làm cho việc duy trì các bài kiểm tra của bạn khó hơn, nhưng đó là điều bạn mong đợi từ kiểm tra bảo hiểm đầy đủ.
Anshul

1
@Finglas Kiểm tra chi tiết thực hiện không sai. Đó là lập trường của bạn về nó, nhưng đây là một chủ đề tranh luận rộng rãi. Kiểm tra nội bộ cung cấp cho bạn một vài lợi thế, mặc dù chúng có thể không được yêu cầu cho bảo hiểm đầy đủ. Các thử nghiệm của bạn tập trung hơn vì bạn đang thử nghiệm các phương thức riêng tư trực tiếp thay vì đi qua API công khai, có thể giới thiệu thử nghiệm của bạn. Thử nghiệm tiêu cực dễ dàng hơn vì bạn có thể vô hiệu hóa thủ công các thành viên riêng tư và đảm bảo rằng API công khai trả về các lỗi thích hợp để đáp ứng với trạng thái nội bộ không nhất quán. Có nhiều ví dụ hơn.
Anshul

9

Nếu bạn đang sử dụng C #, bạn có thể tạo phương thức nội bộ. Bằng cách đó, bạn không gây ô nhiễm API công khai.

Sau đó thêm thuộc tính vào dll

[lắp ráp: InternalsVisibleTo ("MyTestAssugging")]

Bây giờ tất cả các phương thức đều hiển thị trong dự án MyTestAssugging của bạn. Có thể không hoàn hảo, nhưng tốt hơn là làm cho phương thức riêng tư công khai chỉ để kiểm tra nó.


7

Sử dụng sự phản chiếu để truy cập các biến riêng tư nếu bạn cần.

Nhưng thực sự, bạn không quan tâm đến trạng thái bên trong của lớp, bạn chỉ muốn kiểm tra xem các phương thức công khai có trả về những gì bạn mong đợi trong các tình huống bạn có thể dự đoán không.


Bạn sẽ làm gì với một phương thức void?
IntelliData

@IntelliData Sử dụng sự phản chiếu để truy cập các biến riêng tư nếu bạn cần.
Daniel Alexiuc

6

Tôi sẽ nói rằng đó là một ý tưởng tồi cho tôi không chắc bạn có nhận được bất kỳ lợi ích và các vấn đề tiềm ẩn nào không. Nếu bạn đang thay đổi hợp đồng của các cuộc gọi, chỉ để kiểm tra một phương thức riêng tư, bạn sẽ không kiểm tra lớp theo cách nó sẽ được sử dụng, nhưng tạo ra một kịch bản nhân tạo mà bạn không bao giờ có ý định xảy ra.

Hơn nữa, bằng cách tuyên bố phương thức là công khai, những gì sẽ nói rằng trong sáu tháng (sau khi quên rằng lý do duy nhất để công khai phương thức là để thử nghiệm) rằng bạn (hoặc nếu bạn đã bàn giao dự án) thì ai đó đã hoàn toàn khác Không sử dụng nó, dẫn đến những hậu quả không lường trước và / hoặc một cơn ác mộng bảo trì.


1
Điều gì sẽ xảy ra nếu lớp đó là việc thực hiện hợp đồng dịch vụ và nó chỉ được sử dụng qua giao diện của nó - theo cách này ngay cả khi phương thức riêng tư được công khai, dù sao nó cũng sẽ không được gọi vì nó không hiển thị
jcvandan

Tôi vẫn muốn kiểm tra lớp bằng API công khai (giao diện), vì nếu không, nếu bạn cấu trúc lại lớp để tách phương thức nội bộ, thì bạn phải thay đổi các kiểm tra, có nghĩa là bạn sẽ phải chi tiêu một số nỗ lực đảm bảo rằng các bài kiểm tra mới hoạt động giống như các bài kiểm tra cũ.
beny23

Ngoài ra, nếu cách duy nhất để đảm bảo rằng phạm vi kiểm tra bao gồm mọi trường hợp cạnh bên trong một phương thức riêng là gọi trực tiếp, thì có thể chỉ ra rằng độ phức tạp của lớp đảm bảo tái cấu trúc để đơn giản hóa nó.
beny23

@dormisher - Nếu lớp chỉ được sử dụng qua giao diện, thì bạn chỉ cần kiểm tra xem (các) phương thức giao diện công cộng có hoạt động chính xác không. Nếu các phương thức công khai thực hiện những gì họ dự định làm, tại sao bạn quan tâm đến các phương thức riêng tư?
Andrzej Doyle

@Andrzej - Tôi cho rằng tôi không nên thực sự nhưng có những tình huống tôi tin rằng nó có thể hợp lệ, ví dụ: một phương thức riêng xác nhận trạng thái mô hình, có thể đáng thử nghiệm một phương thức như thế này - nhưng một phương pháp như thế này có thể kiểm tra được giao diện công cộng nào
jcvandan

6

về mặt kiểm thử đơn vị, bạn chắc chắn không nên thêm nhiều phương thức; Tôi tin rằng bạn nên làm một trường hợp thử nghiệm tốt hơn về first()phương pháp của bạn , sẽ được gọi trước mỗi thử nghiệm; sau đó bạn có thể gọi nhiều lần - next(), previous()last()để xem các kết quả phù hợp với sự mong đợi của bạn. Tôi đoán nếu bạn không thêm nhiều phương thức vào lớp của mình (chỉ nhằm mục đích thử nghiệm), bạn sẽ tuân thủ nguyên tắc thử nghiệm "hộp đen";


5

Trước tiên hãy xem phương thức nên được trích xuất sang một lớp khác và công khai. Nếu không phải như vậy, hãy làm cho gói được bảo vệ và trong chú thích Java bằng @VisibleForTesting .


5

Trong bản cập nhật của bạn, bạn nói rằng thật tốt khi chỉ kiểm tra bằng API công khai. Thực tế có hai trường học ở đây.

  1. Kiểm tra hộp đen

    Trường hộp đen nói rằng lớp học nên được coi là một hộp đen mà không ai có thể nhìn thấy việc thực hiện bên trong nó. Cách duy nhất để kiểm tra điều này là thông qua API công khai - giống như người dùng của lớp sẽ sử dụng nó.

  2. kiểm tra hộp trắng.

    Trường hộp trắng nghĩ rằng sử dụng kiến ​​thức về việc triển khai lớp một cách tự nhiên và sau đó kiểm tra lớp để biết rằng nó hoạt động như bình thường.

Tôi thực sự không thể đứng về phía thảo luận. Tôi chỉ nghĩ rằng sẽ rất thú vị khi biết rằng có hai cách riêng biệt để kiểm tra một lớp (hoặc thư viện hoặc bất cứ điều gì).


4

Thực tế, có những tình huống khi bạn nên làm điều này (ví dụ: khi bạn đang thực hiện một số thuật toán phức tạp). Chỉ cần làm nó gói riêng tư và điều này là đủ. Nhưng trong hầu hết các trường hợp, có lẽ bạn có các lớp quá phức tạp đòi hỏi phải bao gồm logic vào các lớp khác.


4

Các phương thức riêng tư mà bạn muốn kiểm tra một cách cô lập là một dấu hiệu cho thấy có một "khái niệm" khác được chôn trong lớp của bạn. Trích xuất "khái niệm" đó vào lớp của chính nó và kiểm tra nó như là một "đơn vị" riêng biệt.

Hãy xem video này để có một chủ đề thực sự thú vị.


4

Bạn không bao giờ nên để thử nghiệm của mình ra lệnh. Tôi không nói về TDD hoặc các DD khác, ý tôi là chính xác những gì bạn hỏi. Ứng dụng của bạn cần những phương thức đó để được công khai. Nếu có thì kiểm tra chúng. Nếu không thì đừng làm cho chúng công khai chỉ để thử nghiệm. Tương tự với các biến và những người khác. Hãy để nhu cầu của ứng dụng của bạn ra lệnh mã và để các thử nghiệm của bạn kiểm tra xem nhu cầu đó có được đáp ứng hay không. (Một lần nữa tôi không có nghĩa là thử nghiệm trước hay không. Tôi có nghĩa là thay đổi cấu trúc lớp để đáp ứng mục tiêu thử nghiệm).

Thay vào đó bạn nên "kiểm tra cao hơn". Kiểm tra phương thức gọi phương thức riêng. Nhưng các bài kiểm tra của bạn nên kiểm tra nhu cầu ứng dụng của bạn chứ không phải "quyết định thực hiện" của bạn.

Ví dụ (mã giả Bod ở đây);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

Không có lý do để kiểm tra "thêm" thay vào đó bạn có thể kiểm tra "sách".

Không bao giờ để thử nghiệm của bạn đưa ra quyết định thiết kế mã cho bạn. Kiểm tra rằng bạn nhận được kết quả mong đợi, không phải làm thế nào bạn có được kết quả đó.


1
"Không bao giờ để thử nghiệm của bạn đưa ra quyết định thiết kế mã cho bạn" - Tôi phải không đồng ý. Khó kiểm tra là một mùi mã. Nếu bạn không thể kiểm tra nó, làm sao bạn biết nó không bị hỏng?
Alfred Armstrong

Để làm rõ, tôi đồng ý rằng bạn không nên thay đổi thiết kế API bên ngoài của thư viện. Nhưng bạn có thể cần phải cấu trúc lại để làm cho nó dễ kiểm tra hơn.
Alfred Armstrong

Tôi đã thấy rằng nếu bạn cần cấu trúc lại để kiểm tra, đó không phải là mùi mã bạn nên lo lắng. Bạn có một cái gì đó sai. Tất nhiên đó là kinh nghiệm cá nhân và cả hai chúng ta có thể tranh luận với dữ liệu vững chắc theo cả hai cách. Tôi chỉ chưa bao giờ "khó kiểm tra" mà không phải là "thiết kế kém" ngay từ đầu.
coteyr

Giải pháp này phù hợp với phương pháp như sách trả về giá trị - bạn có thể kiểm tra loại trả về trong một xác nhận ; nhưng làm thế nào bạn sẽ kiểm tra một phương thức void ?
IntelliData

phương pháp void làm một cái gì đó hoặc họ không cần phải tồn tại. Kiểm tra những thứ họ làm,
coteyr

2

Tôi có xu hướng đồng ý rằng các phần thưởng của việc đơn vị này đã thử nghiệm vượt xa các vấn đề làm tăng khả năng hiển thị của một số thành viên. Một cải tiến nhỏ là làm cho nó được bảo vệ và ảo, sau đó ghi đè lên nó trong một lớp thử nghiệm để phơi bày nó.

Ngoài ra, nếu đó là chức năng bạn muốn kiểm tra riêng thì nó có gợi ý đối tượng bị thiếu trong thiết kế của bạn không? Có lẽ bạn có thể đặt nó trong một lớp có thể kiểm tra riêng ... sau đó lớp hiện tại của bạn chỉ ủy quyền cho một thể hiện của lớp mới này.


2

Tôi thường giữ các lớp kiểm tra trong cùng một dự án / lắp ráp như các lớp được kiểm tra.
Bằng cách này, tôi chỉ cần internalkhả năng hiển thị để làm cho các chức năng / lớp có thể kiểm tra được.

Điều này phần nào làm phức tạp quá trình xây dựng của bạn, cần lọc ra các lớp kiểm tra. Tôi đạt được điều này bằng cách đặt tên cho tất cả các lớp kiểm tra của mình TestedClassTestvà sử dụng biểu thức chính quy để lọc các lớp đó.

Điều này tất nhiên chỉ áp dụng cho phần C # / .NET trong câu hỏi của bạn


2

Tôi thường sẽ thêm một phương pháp gọi là một cái gì đó giống như validate, verify, check, vv, để một lớp học để nó có thể được gọi để kiểm tra trạng thái nội tại của một đối tượng.

Đôi khi phương thức này được gói trong một khối ifdef (tôi viết chủ yếu bằng C ++) để nó không được biên dịch để phát hành. Nhưng nó thường hữu ích trong việc phát hành để cung cấp các phương thức xác nhận để kiểm tra các cây đối tượng của chương trình.


2

Quả ổi có chú thích @VisibleForTesting để đánh dấu các phương thức có phạm vi mở rộng (gói hoặc công khai) mà chúng có thể. Tôi sử dụng chú thích @Private cho cùng một thứ.

Mặc dù API công khai phải được kiểm tra, đôi khi thật tiện lợi và hợp lý để có được những thứ thường không được công khai.

Khi nào:

  • một lớp được làm cho ít đáng kể hơn, trong toto, bằng cách chia nó thành nhiều lớp,
  • chỉ để làm cho nó dễ kiểm tra hơn,
  • và cung cấp một số quyền truy cập thử nghiệm vào các bộ phận bên trong sẽ thực hiện các mẹo

có vẻ như tôn giáo đang vấp ngã kỹ thuật.


2

Tôi thường để các phương thức đó protectedvà đặt kiểm tra đơn vị trong cùng một gói (nhưng trong một dự án hoặc thư mục nguồn khác), nơi chúng có thể truy cập tất cả các phương thức được bảo vệ bởi vì trình nạp lớp sẽ đặt chúng trong cùng một không gian tên.


2

Không, bởi vì có những cách tốt hơn để lột da con mèo đó.

Một số khai thác kiểm tra đơn vị dựa trên các macro trong định nghĩa lớp sẽ tự động mở rộng để tạo các móc khi được xây dựng trong chế độ thử nghiệm. Phong cách rất C, nhưng nó hoạt động.

Một thành ngữ OO dễ dàng hơn là làm cho bất cứ điều gì bạn muốn kiểm tra "được bảo vệ" thay vì "riêng tư". Khai thác kiểm tra kế thừa từ lớp được kiểm tra, và sau đó có thể truy cập tất cả các thành viên được bảo vệ.

Hoặc bạn chọn tùy chọn "bạn bè". Cá nhân đây là tính năng của C ++ tôi thích nhất vì nó phá vỡ các quy tắc đóng gói, nhưng nó thực sự cần thiết cho cách C ++ thực hiện một số tính năng, vì vậy hey ho.

Dù sao, nếu bạn đang thử nghiệm đơn vị thì bạn cũng có khả năng cần tiêm các giá trị vào các thành viên đó. Nhắn tin hộp trắng là hoàn toàn hợp lệ. Và điều đó thực sự sẽ phá vỡ đóng gói của bạn.


2

Trong .Net có một lớp đặc biệt được gọi là PrivateObjectđặc biệt để cho phép bạn truy cập các phương thức riêng tư của một lớp.

Xem thêm về nó trên MSDN hoặc ở đây trên Stack Overflow

(Tôi tự hỏi rằng cho đến nay không ai đề cập đến nó.)

Có những tình huống mặc dù điều này là không đủ, trong trường hợp đó bạn sẽ phải sử dụng sự phản chiếu.

Tuy nhiên, tôi vẫn tuân thủ khuyến nghị chung là không thử nghiệm các phương pháp riêng tư, tuy nhiên như thường lệ, luôn có ngoại lệ.


1

Như được lưu ý rộng rãi bởi ý kiến ​​của người khác, các bài kiểm tra đơn vị nên tập trung vào API công khai. Tuy nhiên, ưu / nhược điểm và biện minh sang một bên, bạn có thể gọi các phương thức riêng tư trong một bài kiểm tra đơn vị bằng cách sử dụng sự phản chiếu. Tất nhiên bạn sẽ cần đảm bảo bảo mật JRE của bạn cho phép nó. Gọi các phương thức riêng là thứ mà Spring Framework sử dụng với ReflectionUtils (xem phầnmakeAccessible(Method) phương thức).

Đây là một lớp ví dụ nhỏ với một phương thức cá nhân.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

Và một lớp ví dụ thực thi phương thức cá nhân.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Thực thi B, sẽ in Doing something private. Nếu bạn thực sự cần nó, sự phản chiếu có thể được sử dụng trong các thử nghiệm đơn vị để truy cập các phương thức cá nhân.


0

Cá nhân, tôi có những vấn đề tương tự khi thử nghiệm các phương thức riêng tư và điều này là do một số công cụ kiểm tra bị hạn chế. Thiết kế của bạn không được điều khiển bởi các công cụ hạn chế nếu chúng không đáp ứng nhu cầu của bạn thay đổi công cụ không phải là thiết kế. Vì yêu cầu của bạn về C # tôi không thể đề xuất các công cụ kiểm tra tốt, nhưng đối với Java có hai công cụ mạnh mẽ: TestNG và PowerMock và bạn có thể tìm thấy các công cụ kiểm tra tương ứng cho nền tảng .NET

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.