2011-12-22 44 views
10

Vì vậy, tôi đã được yêu cầu đọc về chế nhạo và BDD cho nhóm phát triển của chúng tôi và chơi xung quanh với mocks để cải thiện một số bài kiểm tra đơn vị hiện có của chúng tôi (như một thử nghiệm). Tôi đã chọn Mockito vì một số lý do (một số bên ngoài phạm vi kiểm soát của tôi), nhưng cụ thể là vì nó hỗ trợ cả hai stubbing và chế nhạo cho các trường hợp khi chế nhạo sẽ không thích hợp.Mockito: Mocking "Blackbox" Dependencies

Tôi đã dành cả ngày để học về Mockito, chế nhạo (nói chung) và BDD. Và bây giờ tôi đã sẵn sàng đào sâu và bắt đầu tăng cường các bài kiểm tra đơn vị của chúng tôi.

Vì vậy, chúng ta có một lớp được gọi là WebAdaptor rằng có một phương pháp run():

public class WebAdaptor { 

    private Subscriber subscriber; 

    public void run() { 

     subscriber = new Subscriber(); 
     subscriber.init(); 
    } 
} 

Xin lưu ý: (! Vì những lý do ngoài phạm vi của câu hỏi này) Tôi không có một cách để sửa đổi mã này . Do đó, tôi không phải có khả năng thêm phương thức setter cho Subscriber và do đó, nó có thể được coi là "hộp đen" không thể truy cập được bên trong của tôi WebAdaptor.

Tôi muốn viết một bài kiểm tra đơn vị kết hợp một mô hình Mockito và sử dụng mô phỏng đó verify thực hiện WebAdaptor::run() làm cho số Subscriber::init() được gọi.

Vì vậy, đây là những gì tôi đã có cho đến nay (bên WebAdaptorUnitTest):

@Test 
public void runShouldInvokeSubscriberInit() { 

    // Given 
    Subscriber mockSubscriber = mock(Subscriber.class); 
    WebAdaptor adaptor = new WebAdaptor(); 

    // When 
    adaptor.run(); 

    // Then 
    verify(mockSubscriber).init(); 
} 

Khi tôi chạy thử nghiệm này, phương pháp thực tế Subscriber::init() được thực hiện (tôi có thể nói từ giao diện điều khiển đầu ra và các tập tin nhìn thấy được tạo ra trên hệ thống cục bộ của tôi), không phải số mockSubscriber, không được thực hiện (hoặc trả lại) bất kỳ thứ gì.

Tôi đã kiểm tra và tái kiểm tra: initpublic, không phải là static hoặc final, và nó trả void. Theo các tài liệu, Mockito sẽ không có vấn đề nhạo báng đối tượng này.

Vì vậy, điều đó khiến tôi suy nghĩ: tôi có cần phải liên kết rõ ràng mockSubscriber với adaptor không? Nếu đây là một trường hợp, sau đó bình thường, sau đây sẽ thường sửa chữa nó:

adaptor.setSubscriber(mockSubscriber); 

Nhưng kể từ khi tôi không thể thêm bất kỳ setter như vậy (xin đọc lưu ý của tôi ở trên), tôi đang ở một mất mát như thế nào tôi có thể buộc một hiệp hội như vậy. Vì vậy, một số câu hỏi có liên quan chặt chẽ:

  • Bất kỳ ai có thể xác nhận rằng tôi đã thiết lập kiểm tra chính xác (sử dụng API Mockito)?
  • Nghi ngờ của tôi về người đặt thiếu có đúng không? (Tôi có cần phải liên kết các đối tượng này thông qua một setter không?)
  • Nếu nghi ngờ trên của tôi là đúng và tôi không thể sửa đổi WebAdaptor, có bất kỳ vi phạm nào khi xử lý không?

Xin cảm ơn trước!

+0

này không trực tiếp trả lời câu hỏi của bạn, nhưng JMockIt làm cho loại hộp đen chế giễu khá dễ dàng. JMockIt là một lựa chọn cho bạn? –

+0

Người đăng ký đã khởi tạo như thế nào trong lớp học này? Có thể ghi đè lên mã instantiating để trả về một thể hiện mà bạn kiểm soát? –

+0

run() là phương thức duy nhất sử dụng Subscriber, do đó, tất cả có nghĩa là nó phải là một biến cục bộ bên trong phương thức đó. Một lần nữa, tôi không thể thay đổi mã ... – IAmYourFaja

Trả lời

10

Bạn cần phải tiêm mô hình vào lớp mà bạn đang thử nghiệm.Bạn không cần quyền truy cập vào Người đăng ký. Cách mockito và các khuôn khổ mocking khác giúp đỡ là bạn không cần truy cập vào các đối tượng mà bạn đang tương tác. Tuy nhiên, bạn cần một cách để đưa các đối tượng giả vào lớp bạn đang thử nghiệm.

public class WebAdaptor { 

    public WebAdaptor(Subscriber subscriber) { /* Added a new constructor */ 
     this.subscriber = subscriber; 
    } 

    private Subscriber subscriber; 

    public void run() { 
     subscriber.init(); 
    } 
} 

Bây giờ bạn có thể xác minh tương tác của bạn trên mô hình, thay vì trên đối tượng thực.

@Test 
public void runShouldInvokeSubscriberInit() { 

    // Given 
    Subscriber mockSubscriber = mock(Subscriber.class); 
    WebAdaptor adaptor = new WebAdaptor(mockSubscriber); // Use the new constructor 

    // When 
    adaptor.run(); 

    // Then 
    verify(mockSubscriber).init(); 
} 

Nếu thêm các thuê bao để các nhà xây dựng không phải là cách tiếp cận đúng, bạn cũng có thể xem xét sử dụng một nhà máy để cho phép WebAdaptor để nhanh chóng đối tượng thuê bao mới từ một nhà máy mà bạn kiểm soát. Sau đó bạn có thể chế tạo nhà máy để cung cấp cho người đăng ký giả lập.

+0

Đoạn mã của bạn cho WebAdaptor vẫn có phương thức run() tạo một Người đăng ký mới. FYI. –

+0

David V - sau khi nghe bản thân mình to (như tôi đã viết câu hỏi này), kết hợp với câu trả lời của bạn và các bình luận ở trên, có vẻ như việc thay đổi mã 'WebAdaptor' là lựa chọn duy nhất của tôi. Cám ơn phản hồi của bạn! – IAmYourFaja

+2

Để giữ cho lớp WebAdaptor của bạn làm việc với mã hiện có, bạn cũng có thể muốn có một hàm tạo no-arg gọi hàm tạo mới của bạn. Sau đó, các tập quán không kiểm tra hiện có của lớp có thể sử dụng hàm tạo no-arg. Vì vậy, hàm tạo mới sẽ là 'public WebAdaptor() {this (new Subscriber());}'. Ngoài ra, hàm tạo có đối số Người đăng ký phải là gói riêng tư. –

5

Nếu bạn không muốn thay đổi mã sản xuất và vẫn có thể mô phỏng chức năng của lớp Người đăng ký, bạn nên xem PowerMock. Nó hoạt động tốt cùng với Mockito và cho phép bạn giả lập việc tạo ra các đối tượng mới.

Subscriber mockSubscriber = mock(Subscriber.class); 
whenNew(Subscriber.class).withNoArguments().thenReturn(mockSubscriber); 

Chi tiết khác được giải thích trong khuôn khổ documentation for the PowerMock.

+0

Đọc [Ví dụ # 3 - Xây dựng mô hình đối tượng mới] (http://blog.jayway.com/2009/10/28/untestable-code-with-mockito-and-powermock/) (cuộn xuống) để xem thêm ví dụ đầy đủ. – matsev

2

Có cách để đưa mô phỏng của bạn vào lớp đang được kiểm tra mà không thực hiện bất kỳ sửa đổi nào đối với mã. Điều này có thể được thực hiện bằng cách sử dụng Mockito WhiteBox. Đây là một tính năng rất tốt có thể được sử dụng để tiêm các phụ thuộc của lớp dưới kiểm tra của bạn từ các bài kiểm tra của bạn. Sau đây là một ví dụ đơn giản về cách thức hoạt động,

@Mock 
Subscriber mockSubscriber; 
WebAdaptor cut = new WebAdaptor(); 

@Before 
public void setup(){ 
    //sets the internal state of the field in the class under test even if it is private 
    MockitoAnnotations.initMocks(this); 

    //Now the whitebox functionality injects the dependent object - mockSubscriber 
    //into the object which depends on it - cut 
    Whitebox.setInternalState(cut, "subscriber", mockSubscriber); 
} 

@Test 
public void runShouldInvokeSubscriberInit() { 
    cut.run(); 
    verify(mockSubscriber).init(); 
} 

Hope this helps :-)

+0

@zharvey có hữu ích không? hoặc tôi đã nhầm? – Bala

+2

Điều này sẽ không hoạt động. Khi cut.run() được gọi là một cá thể mới, không được mô phỏng, Subscriber sẽ được tạo bên trong WebAdapter và sẽ thay thế phiên bản được chế nhạo. Phương thức init trên cá thể mới sẽ được gọi. –

+0

Có, bạn đã đúng. Tôi nhớ nó. – Bala

1

Bạn không có thể thử các thuê bao sử dụng Mockito trong việc thực hiện hiện tại của bạn.

Sự cố bạn có là Người đăng ký được xây dựng và sau đó truy cập ngay lập tức, Mockito không có khả năng thay thế (hoặc gián điệp) phiên bản Người đăng ký sau khi tạo nhưng trước khi phương thức init được gọi.

public void run() { 

    subscriber = new Subscriber(); 
    // Mockito would need to jump in here 
    subscriber.init(); 
} 

Câu trả lời của David V giải quyết vấn đề này bằng cách thêm Người đăng ký vào người xây dựng. Một giải pháp thay thế giữ lại cấu trúc Subscriber ẩn sẽ là khởi tạo Subscriber trong một hàm tạo WebAdapter no-arg và sau đó sử dụng sự phản chiếu để thay thế cá thể đó trước khi gọi phương thức run.

WebAdapter của bạn sẽ trông như thế này,

public class WebAdaptor { 

    private Subscriber subscriber; 

    public WebAdaptor() { 
     subscriber = new Subscriber(); 
    } 

    public void run() {    
     subscriber.init(); 
    } 
} 

Và bạn có thể sử dụng ReflectionTestUtils từ mô-đun thử nghiệm Spring Framework để tiêm phụ thuộc vào đó lĩnh vực tư nhân.

@Test 
public void runShouldInvokeSubscriberInit() { 

    // Given 
    Subscriber mockSubscriber = mock(Subscriber.class); 
    WebAdaptor adaptor = new WebAdaptor(); 
    ReflectionTestUtils.setField(adaptor "subscriber", mockSubscriber); 

    // When 
    adaptor.run(); // This will call mockSubscriber.init() 

    // Then 
    verify(mockSubscriber).init(); 
} 

ReflectionTestUtils thực sự chỉ là một wrapper về phản ánh của Java, như vậy có thể đạt được bằng tay (và nhiều hơn một cách chi tiết hơn) mà không phụ thuộc mùa xuân.

Mockito's WhiteBox (như Bala gợi ý) sẽ hoạt động tại đây thay cho ReflectionTestUtils, nó nằm trong gói nội bộ của Mockito nên tôi né tránh nó, YMMV.

2

Bạn có thể đã sử dụng PowerMock để thử cuộc gọi constructor mà không thay đổi mã gốc:

import org.mockito.Mockito; 
import org.powermock.api.mockito.PowerMockito; 
import org.powermock.core.classloader.annotations.PrepareForTest; 
import org.powermock.modules.junit4.PowerMockRunner; 

@RunWith(PowerMockRunner.class) 
@PrepareForTest(WebAdaptor.class) 
public class WebAdaptorTest { 
    @Test 
    public void testRunCallsSubscriberInit() { 
     final Subscriber subscriber = mock(Subscriber.class); 
     whenNew(Subscriber.class).withNoArguments().thenReturn(subscriber); 
     new WebAdaptor().run(); 
     verify(subscriber).init(); 
    } 
}