Làm thế nào để bạn đơn vị thử nghiệm phương pháp riêng tư?


184

Tôi đang làm việc trên một dự án java. Tôi mới đến thử nghiệm đơn vị. Cách tốt nhất để đơn vị kiểm tra các phương thức riêng tư trong các lớp java là gì?


1
Kiểm tra câu hỏi này trên StackOverflow. Một vài kỹ thuật được đề cập và thảo luận. Cách tốt nhất để kiểm tra đơn vị phương pháp riêng là gì?
Chiron

34
Ý kiến ​​của tôi luôn là các phương pháp riêng tư không cần thử nghiệm vì bạn nên thử nghiệm những gì có sẵn. Một phương pháp công khai. Nếu bạn không thể phá vỡ phương thức công khai, liệu phương thức riêng tư đó có thực sự quan trọng không?
Giàn khoan

1
Cả hai phương pháp công cộng và tư nhân nên được thử nghiệm. Do đó, một trình điều khiển kiểm tra thường cần phải ở trong lớp mà nó kiểm tra. như này .
trao đổi quá mức

2
@Rig - +1 - bạn sẽ có thể gọi tất cả các hành vi cần thiết của một phương thức riêng tư từ các phương thức công khai của bạn - nếu bạn không thể thì chức năng không bao giờ có thể được gọi bằng mọi cách vì vậy không có điểm nào trong việc kiểm tra nó.
James Anderson

Sử dụng @Jailbreaktừ khung Manifold để truy cập trực tiếp các phương thức riêng tư. Bằng cách này, mã kiểm tra của bạn vẫn an toàn và dễ đọc. Trên tất cả, không thỏa hiệp thiết kế, không có phương pháp và trường quá mức cho mục đích thử nghiệm.
Scott

Câu trả lời:


239

Bạn thường không thử nghiệm phương pháp riêng tư trực tiếp. Vì chúng là riêng tư, hãy xem chúng là một chi tiết thực hiện. Không ai sẽ gọi một trong số họ và mong đợi nó hoạt động theo một cách riêng.

Thay vào đó bạn nên kiểm tra giao diện công cộng của bạn. Nếu các phương thức gọi các phương thức riêng tư của bạn đang hoạt động như bạn mong đợi, thì bạn giả sử bằng cách mở rộng rằng các phương thức riêng tư của bạn đang hoạt động chính xác.


39
1 triệu đồng. Và nếu một phương thức riêng tư không bao giờ được gọi, đừng kiểm tra đơn vị, hãy xóa nó!
Steven A. Lowe

246
Tôi không đồng ý. Đôi khi một phương thức riêng tư chỉ là một chi tiết triển khai, nhưng nó vẫn đủ phức tạp để nó đảm bảo thử nghiệm, để đảm bảo nó hoạt động đúng. Giao diện công cộng có thể cung cấp một mức độ trừu tượng quá cao để viết một bài kiểm tra nhắm trực tiếp vào thuật toán cụ thể này. Không phải lúc nào cũng khả thi để đưa nó vào một lớp riêng, do dữ liệu được chia sẻ. Trong trường hợp này, tôi muốn nói rằng nó ổn khi thử nghiệm một phương pháp riêng tư.
quant_dev

10
Tất nhiên xóa nó đi. Lý do duy nhất có thể để không xóa một chức năng bạn không sử dụng là nếu bạn nghĩ rằng bạn có thể cần nó trong tương lai, và thậm chí sau đó bạn nên xóa nó nhưng lưu ý chức năng trong nhật ký cam kết của bạn, hoặc ít nhất là nhận xét nó để mọi người biết đó là công cụ bổ sung mà họ có thể bỏ qua.
jhocking

38
@quant_dev: Đôi khi một lớp cần được cấu trúc lại thành một vài lớp khác. Dữ liệu chia sẻ của bạn cũng có thể được kéo vào một lớp riêng biệt. Nói, một lớp "bối cảnh". Sau đó, hai lớp mới của bạn có thể tham khảo lớp ngữ cảnh cho dữ liệu được chia sẻ của họ. Điều này có vẻ không hợp lý, nhưng nếu các phương thức riêng tư của bạn đủ phức tạp để cần thử nghiệm riêng lẻ, thì đó là mùi mã cho biết biểu đồ đối tượng của bạn cần trở nên chi tiết hơn một chút.
Phil

43
Câu trả lời này KHÔNG trả lời câu hỏi. Câu hỏi đặt ra là các phương pháp riêng nên được kiểm tra như thế nào chứ không phải liệu chúng có nên được kiểm tra hay không. Liệu một phương pháp riêng có nên được kiểm tra đơn vị hay không là một câu hỏi thú vị và đáng để tranh luận, nhưng không phải ở đây . Câu trả lời thích hợp là thêm một nhận xét trong câu hỏi nói rằng thử nghiệm các phương pháp riêng tư có thể không phải là một ý tưởng hay và đưa ra một liên kết đến một câu hỏi riêng biệt sẽ đi sâu vào vấn đề hơn.
TallGuy

118

Nói chung, tôi sẽ tránh nó. Nếu phương thức riêng tư của bạn phức tạp đến mức nó cần một bài kiểm tra đơn vị riêng biệt, điều đó thường có nghĩa là nó xứng đáng với lớp riêng của nó. Điều này có thể khuyến khích bạn viết nó theo cách có thể tái sử dụng. Sau đó, bạn nên kiểm tra lớp mới và gọi giao diện chung của nó trong lớp cũ.

Mặt khác, đôi khi bao gồm các chi tiết triển khai thành các lớp riêng biệt dẫn đến các lớp có giao diện phức tạp, nhiều dữ liệu chuyển giữa lớp cũ và lớp mới hoặc đến một thiết kế có thể trông tốt từ quan điểm OOP, nhưng không khớp các trực giác đến từ miền vấn đề (ví dụ: chia mô hình định giá thành hai phần chỉ để tránh thử nghiệm các phương thức riêng tư không trực quan và có thể dẫn đến các vấn đề sau này khi duy trì / mở rộng mã). Bạn không muốn có "lớp sinh đôi" luôn thay đổi cùng nhau.

Khi phải đối mặt với sự lựa chọn giữa đóng gói và kiểm tra, tôi muốn đi thứ hai. Điều quan trọng hơn là phải có mã chính xác (nghĩa là tạo ra đầu ra chính xác) hơn là một thiết kế OOP đẹp không hoạt động chính xác, vì nó không được kiểm tra đầy đủ. Trong Java, bạn chỉ cần cung cấp quyền truy cập "mặc định" cho phương thức và đặt kiểm tra đơn vị trong cùng một gói. Các thử nghiệm đơn vị chỉ là một phần của gói bạn đang phát triển và không có sự phụ thuộc giữa các thử nghiệm và mã đang được thử nghiệm. Điều đó có nghĩa là khi bạn thay đổi triển khai, bạn có thể cần thay đổi các thử nghiệm của mình, nhưng không sao - mỗi thay đổi của việc triển khai đều yêu cầu kiểm tra lại mã và nếu các thử nghiệm cần được sửa đổi để thực hiện điều đó, thì bạn chỉ cần làm nó

Nói chung, một lớp có thể cung cấp nhiều hơn một giao diện. Có một giao diện cho người dùng, và một giao diện cho những người bảo trì. Cái thứ hai có thể phơi bày nhiều hơn để đảm bảo rằng mã được kiểm tra đầy đủ. Nó không phải là một bài kiểm tra đơn vị trên một phương thức riêng tư - ví dụ, có thể là ghi nhật ký. Ghi nhật ký cũng "phá vỡ đóng gói", nhưng chúng tôi vẫn làm điều đó, vì nó rất hữu ích.


9
+1 Điểm thú vị. Cuối cùng, đó là về khả năng bảo trì, không tuân thủ "quy tắc" của OOP tốt.
Phil

9
+1 Tôi hoàn toàn đồng ý với khả năng kiểm tra đóng gói.
Richard

31

Thử nghiệm các phương pháp riêng sẽ phụ thuộc vào độ phức tạp của chúng; một số phương thức riêng tư một dòng sẽ không thực sự đảm bảo nỗ lực thử nghiệm thêm (điều này cũng có thể nói về phương pháp công cộng), nhưng một số phương pháp riêng có thể phức tạp như phương thức công khai và khó kiểm tra qua giao diện công cộng.

Kỹ thuật ưa thích của tôi là làm cho gói phương thức riêng tư thành riêng tư, cho phép truy cập vào một bài kiểm tra đơn vị trong cùng một gói nhưng nó vẫn sẽ được gói gọn từ tất cả các mã khác. Điều này sẽ mang lại lợi thế cho việc kiểm tra logic phương thức riêng trực tiếp thay vì phải dựa vào kiểm tra phương thức công khai để bao quát tất cả các phần của logic phức tạp (có thể).

Nếu điều này được kết hợp với chú thích @VisibleForTesting trong thư viện Google Guava, thì bạn đang đánh dấu rõ ràng phương thức riêng tư của gói này là hiển thị để chỉ kiểm tra và do đó, không nên gọi bất kỳ lớp nào khác.

Những người phản đối kỹ thuật này cho rằng điều này sẽ phá vỡ sự đóng gói và mở các phương thức riêng tư để mã hóa trong cùng một gói. Mặc dù tôi đồng ý rằng điều này phá vỡ đóng gói và mở mã riêng cho các lớp khác, tôi cho rằng việc kiểm tra logic phức tạp quan trọng hơn việc đóng gói chặt chẽ và không sử dụng các phương thức riêng tư được đánh dấu rõ ràng để kiểm tra chỉ phải là trách nhiệm của các nhà phát triển sử dụng và thay đổi cơ sở mã.

Phương pháp riêng trước khi thử nghiệm:

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

Gói phương thức riêng tư đã sẵn sàng để thử nghiệm:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

Lưu ý: Đặt các bài kiểm tra trong cùng một gói không tương đương với việc đặt chúng vào cùng một thư mục vật lý. Việc tách mã chính và mã kiểm tra của bạn thành các cấu trúc thư mục vật lý riêng biệt là cách thực hành tốt nói chung nhưng kỹ thuật này sẽ hoạt động miễn là các lớp được định nghĩa như trong cùng một gói.


14

Nếu bạn không thể sử dụng API bên ngoài hoặc không muốn, bạn vẫn có thể sử dụng API JDK tiêu chuẩn thuần túy để truy cập các phương thức riêng tư bằng cách sử dụng sự phản chiếu. Đây là một ví dụ

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Kiểm tra Hướng dẫn Java http://docs.oracle.com/javase/tutorial/reflect/ hoặc API Java http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. html để biết thêm thông tin.

Như @kij đã trích dẫn câu trả lời của mình, có những lúc một giải pháp đơn giản sử dụng sự phản chiếu thực sự tốt để thử nghiệm một phương pháp riêng tư.


9

Trường hợp kiểm thử đơn vị có nghĩa là kiểm tra đơn vị mã. Điều đó không có nghĩa là kiểm tra giao diện vì nếu bạn đang kiểm tra giao diện, điều đó không có nghĩa là bạn đang kiểm tra đơn vị mã. Nó trở thành một loại thử nghiệm hộp đen. Ngoài ra, tốt hơn là tìm các sự cố ở cấp đơn vị nhỏ nhất hơn là xác định các sự cố ở cấp giao diện và sau đó cố gắng gỡ lỗi phần nào không hoạt động. Do đó, trường hợp thử nghiệm đơn vị nên được kiểm tra bất kể phạm vi của chúng. Sau đây là một cách để kiểm tra các phương pháp riêng tư.

Nếu bạn đang sử dụng java, bạn có thể sử dụng jmockit cung cấp Deencapsulation.invoke để gọi bất kỳ phương thức riêng tư nào của lớp đang thử nghiệm. Nó sử dụng sự phản chiếu để gọi nó cuối cùng nhưng cung cấp một trình bao bọc đẹp xung quanh nó. ( https://code.google.com.vn/p/jmockit/ )


Làm thế nào để trả lời câu hỏi này?
gnat

@gnat, Câu hỏi đặt ra là cách tốt nhất để kiểm thử các phương thức riêng tư trong các lớp java là gì? Và điểm đầu tiên của tôi trả lời câu hỏi.
agrawalankur

Điểm đầu tiên của bạn chỉ đơn thuần là quảng cáo một công cụ, nhưng nó không giải thích lý do tại sao bạn tin rằng nó tốt hơn các lựa chọn thay thế, chẳng hạn như thử nghiệm các phương pháp riêng tư gián tiếp như được đề xuất và giải thích trong câu trả lời hàng đầu
gnat

jmockit đã di chuyển và trang chính thức cũ chỉ ra một bài viết về "Nước tiểu tổng hợp và tiểu nhân tạo". Tuy nhiên, bằng cách này vẫn liên quan đến công cụ nhạo báng, nhưng không liên quan ở đây :) Trang chính thức mới là: jmockit.github.io
Guillaume

8

Trước hết, như các tác giả khác đề xuất: hãy suy nghĩ kỹ nếu bạn thực sự cần thử nghiệm phương pháp riêng tư. Và nếu thế, ...

Trong .NET, bạn có thể chuyển đổi nó thành phương thức "Internal" và tạo gói " InternalVisible " cho dự án thử nghiệm đơn vị của bạn.

Trong Java, bạn có thể tự viết các bài kiểm tra trong lớp để kiểm tra và các phương thức kiểm tra của bạn cũng có thể gọi các phương thức riêng tư. Tôi thực sự không có kinh nghiệm Java lớn, vì vậy đó có lẽ không phải là cách thực hành tốt nhất.

Cảm ơn.


7

Tôi thường làm cho các phương pháp như vậy được bảo vệ. Giả sử lớp học của bạn ở:

src/main/java/you/Class.java

Bạn có thể tạo một lớp kiểm tra như:

src/test/java/you/TestClass.java

Bây giờ bạn có quyền truy cập vào các phương thức được bảo vệ và có thể đơn vị kiểm tra chúng (JUnit hoặc TestNG không thực sự quan trọng), nhưng bạn vẫn giữ các phương thức này từ những người gọi mà bạn không muốn.

Lưu ý rằng điều này mong đợi một cây nguồn kiểu maven.


3

Nếu bạn thực sự cần thử nghiệm phương thức riêng tư, với ý tôi là Java, bạn có thể sử dụng fest assertvà / hoặc fest reflect. Nó sử dụng sự phản chiếu.

Nhập thư viện bằng maven (phiên bản đã cho không phải là phiên bản mới nhất tôi nghĩ) hoặc nhập trực tiếp vào thư viện lớp của bạn:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Ví dụ: nếu bạn có một lớp có tên 'MyClass' với phương thức riêng có tên 'myPrivateMethod', lấy Chuỗi làm tham số để cập nhật giá trị của nó thành 'đây là thử nghiệm tuyệt vời!', Bạn có thể thực hiện bài kiểm tra Junit sau:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Thư viện này cũng cho phép bạn thay thế bất kỳ thuộc tính bean nào (bất kể chúng là riêng tư và không có setters nào được viết) bằng một giả, và sử dụng nó với Mockito hoặc bất kỳ khung giả nào khác thực sự tuyệt vời. Điều duy nhất bạn phải biết vào lúc này (không biết liệu điều này sẽ tốt hơn trong các phiên bản tiếp theo hay không) là tên của trường mục tiêu / phương thức bạn muốn thao tác và chữ ký của nó.


2
Mặc dù sự phản chiếu sẽ cho phép bạn kiểm tra các phương thức riêng tư, nhưng nó không tốt cho việc phát hiện việc sử dụng chúng. Nếu bạn thay đổi tên của một phương thức riêng tư, điều này sẽ phá vỡ bài kiểm tra của bạn.
Richard

1
Bài kiểm tra sẽ phá vỡ có, nhưng đây là một vấn đề nhỏ theo quan điểm của tôi. Tất nhiên tôi hoàn toàn đồng ý về lời giải thích của bạn bên dưới về khả năng hiển thị của gói (với các lớp kiểm tra trong các thư mục riêng biệt nhưng với cùng một gói), nhưng đôi khi bạn không thực sự có sự lựa chọn Ví dụ: nếu bạn có một nhiệm vụ thực sự ngắn trong một doanh nghiệp không áp dụng các phương thức "tốt" (các lớp kiểm tra không trong cùng một gói, v.v.), nếu bạn không có thời gian để cấu trúc lại mã hiện có mà bạn đang làm việc / thử nghiệm, đây vẫn là một thay thế tốt.
kij

2

Những gì tôi thường làm trong C # là làm cho các phương thức của tôi được bảo vệ và không riêng tư. Đó là một công cụ sửa đổi truy cập riêng tư ít hơn một chút, nhưng nó ẩn phương thức khỏi tất cả các lớp không kế thừa từ lớp đang thử nghiệm.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Bất kỳ lớp nào không kế thừa trực tiếp từ classUnderTest đều không biết rằng phương thức ToTest thậm chí còn tồn tại. Trong mã thử nghiệm của mình, tôi có thể tạo một lớp thử nghiệm đặc biệt mở rộng và cung cấp quyền truy cập vào phương thức này ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

Lớp này chỉ tồn tại trong dự án thử nghiệm của tôi. Mục đích duy nhất của nó là cung cấp quyền truy cập vào phương pháp duy nhất này. Nó cho phép tôi truy cập vào những nơi mà hầu hết các lớp khác không có.


4
protectedlà một phần của API công khai và phải chịu những hạn chế tương tự (không bao giờ có thể thay đổi, phải được ghi lại, ...). Trong C # bạn có thể sử dụng internalcùng với InternalsVisibleTo.
CodeInChaos

1

Bạn có thể kiểm tra các phương thức riêng tư một cách dễ dàng nếu bạn đặt các bài kiểm tra đơn vị của mình trong một lớp bên trong vào lớp bạn đang kiểm tra. Sử dụng TestNG, các bài kiểm tra đơn vị của bạn phải là các lớp bên trong tĩnh công khai được chú thích bằng @Test, như thế này:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Vì nó là một lớp bên trong, nên phương thức riêng có thể được gọi.

Các thử nghiệm của tôi được chạy từ maven và nó tự động tìm thấy các trường hợp thử nghiệm này. Nếu bạn chỉ muốn kiểm tra một lớp, bạn có thể làm

$ mvn test -Dtest=*UserEditorTest

Nguồn: http://www.ninthavenue.com.au/how-to-unit-test-private-methods


4
Bạn đang đề xuất bao gồm mã kiểm tra (và các lớp bên trong bổ sung) trong mã thực tế của gói được triển khai?

1
Lớp kiểm tra là một lớp bên trong của lớp bạn muốn kiểm tra. Nếu bạn không muốn các lớp bên trong đó được triển khai, bạn có thể xóa các tệp * Test. Class khỏi gói của bạn.
Roger Keays

1
Tôi không thích tùy chọn này, nhưng đối với nhiều người, đây có lẽ là một giải pháp hợp lệ, không đáng để xem thường.
Bill K

@RogerKeays: Về mặt kỹ thuật khi bạn triển khai, làm thế nào để bạn loại bỏ "các lớp bên trong thử nghiệm" như vậy? Xin đừng nói rằng bạn cắt chúng bằng tay.
gyorgyabraham

Tôi không loại bỏ chúng. Đúng. Tôi triển khai mã không bao giờ được thực thi trong thời gian chạy. Nó thực sự là xấu.
Roger Keays
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.