Mockito khi gọi () hoạt động như thế nào?


111

Đưa ra câu lệnh Mockito sau:

when(mock.method()).thenReturn(someValue);

Làm cách nào để Mockito tạo ra một thứ gì đó ủy quyền cho một mô hình, với điều kiện rằng câu lệnh mock.method () sẽ chuyển giá trị trả về cho when ()? Tôi tưởng tượng rằng điều này sử dụng một số công cụ CGLib, nhưng sẽ muốn biết cách thực hiện về mặt kỹ thuật.

Câu trả lời:


118

Câu trả lời ngắn gọn là trong ví dụ của bạn, kết quả của mock.method()sẽ là một giá trị trống phù hợp với kiểu; mockito sử dụng hướng dẫn thông qua ủy quyền, đánh chặn phương thức và một phiên bản được chia sẻ của MockingProgresslớp để xác định xem liệu một lệnh gọi một phương thức trên một mô hình là để khai thác hoặc phát lại một hành vi sơ khai hiện có thay vì truyền thông tin về khai thác thông qua giá trị trả về một phương pháp chế nhạo.

Một phân tích nhỏ trong vài phút về mã mockito như sau. Lưu ý, đây là một mô tả rất thô - có rất nhiều chi tiết đang chơi ở đây. Tôi khuyên bạn nên tự mình kiểm tra nguồn trên github .

Đầu tiên, khi bạn mô phỏng một lớp bằng cách sử dụng mockphương thức của Mockitolớp, đây về cơ bản là những gì sẽ xảy ra:

  1. Mockito.mock đại biểu cho org.mockito.internal.MockitoCore .mock, chuyển cài đặt giả lập mặc định dưới dạng tham số.
  2. MockitoCore.mockđại biểu cho org.mockito.internal.util.MockUtil.createMock
  3. Các MockUtillớp học sử dụng các ClassPathLoaderlớp để có được một thể hiện của MockMakersử dụng để tạo ra các mô hình. Theo mặc định, lớp CgLibMockMaker được sử dụng.
  4. CgLibMockMakersử dụng một lớp được mượn từ JMock ClassImposterizerđể xử lý việc tạo mô hình. Các phần chính của 'ma thuật mockito' được MethodInterceptorsử dụng để tạo ra mô hình: mockito MethodInterceptorFilter, và một chuỗi các trường hợp MockHandler, bao gồm cả một phiên bản MockHandlerImpl . Bộ đánh chặn phương thức chuyển lời gọi đến cá thể MockHandlerImpl, thực hiện logic nghiệp vụ sẽ được áp dụng khi một phương thức được gọi trên một mô hình (tức là, tìm kiếm để xem liệu câu trả lời đã được ghi lại chưa, xác định xem lời gọi đó có đại diện cho một sơ khai mới hay không, v.v. Trạng thái mặc định là nếu một sơ khai chưa được đăng ký cho phương thức đang được gọi, một giá trị trống phù hợp với kiểu sẽ được trả về.

Bây giờ, hãy xem mã trong ví dụ của bạn:

when(mock.method()).thenReturn(someValue)

Đây là thứ tự mà mã này sẽ thực thi:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

Chìa khóa để hiểu điều gì đang xảy ra là điều gì sẽ xảy ra khi phương thức trên mô hình được gọi: bộ đánh chặn phương thức được chuyển thông tin về lời gọi phương thức và ủy quyền cho chuỗi các MockHandlertrường hợp của nó , mà cuối cùng sẽ ủy quyền MockHandlerImpl#handle. Trong khi đó MockHandlerImpl#handle, trình xử lý giả tạo một thể hiện của OngoingStubbingImplvà chuyển nó đến thể hiện được chia sẻ MockingProgress.

Khi whenphương thức được gọi sau lời gọi method(), nó sẽ ủy quyền cho MockitoCore.when, stub()phương thức này sẽ gọi phương thức của cùng một lớp. Phương thức này giải nén phần sơ khai đang diễn ra từ MockingProgresstrường hợp được chia sẻ mà method()lời gọi giả mạo đã viết vào và trả về nó. Sau đó, thenReturnphương thức sau đó được gọi trên OngoingStubbingcá thể.


1
Cảm ơn đã phản ứng chi tiết. Một câu hỏi khác - bạn đề cập rằng "khi nào phương thức được gọi sau khi gọi phương thức ()" - làm thế nào nó biết rằng lời gọi của when () là lời gọi tiếp theo (hoặc kết thúc) lời gọi của phương thức ()? Hy vọng điều đó có ý nghĩa.
marchaos

@marchaos Nó không biết. Với when(mock.method()).thenXyz(...)cú pháp, mock.method()được thực thi ở chế độ "phát lại", không phải ở chế độ "khai thác". Thông thường, thực hiện này mock.method()không có tác dụng, vì vậy sau khi thenXyz(...)( thenReturn, thenThrow, thenAnswer, vv) được thực thi, nó đi vào "stubbing" chế độ và sau đó ghi lại kết quả mong muốn cho rằng lời gọi phương thức.
Rogério

1
Rogerio, nó thực sự tinh tế hơn thế một chút - mockito không có các chế độ phát và phát lại rõ ràng. Tôi sẽ chỉnh sửa câu trả lời của mình sau để nó rõ ràng hơn.
Paul Morie

Tóm lại, tôi sẽ tóm tắt lại rằng, sẽ dễ dàng hơn để chặn một cuộc gọi phương thức trong một phương thức khác với CGLIB hoặc Javassist để chặn, ví dụ, toán tử "if".
Infeligo

Tôi chưa xoa bóp mô tả của mình ở đây, nhưng tôi cũng chưa quên về nó. FYI.
Paul Morie

33

Câu trả lời ngắn gọn là, đằng sau hậu trường, Mockito sử dụng một số loại biến / lưu trữ toàn cục để lưu thông tin về các bước xây dựng sơ khai phương thức (gọi phương thức (), when (), thenReturn () trong ví dụ của bạn), để cuối cùng nó có thể xây dựng bản đồ về những gì sẽ được trả lại khi những gì được gọi trên tham số nào.

Tôi thấy bài viết này rất hữu ích: Giải thích cách hoạt động của Mock Frameworks dựa trên proxy ( http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html ). Tác giả đã triển khai một khuôn khổ Mocking trình diễn, mà tôi tìm thấy một tài nguyên rất tốt cho những người muốn tìm hiểu cách hoạt động của các khuôn khổ Mocking này.

Theo tôi, đó là cách sử dụng Anti-Pattern điển hình. Thông thường, chúng ta nên tránh 'tác dụng phụ' khi chúng ta triển khai một phương thức, nghĩa là phương thức phải chấp nhận đầu vào và thực hiện một số phép tính và trả về kết quả - không có gì khác thay đổi ngoài điều đó. Nhưng Mockito chỉ cố ý vi phạm quy tắc đó. Các phương thức của nó lưu trữ một loạt thông tin bên cạnh việc trả về kết quả: Mockito.anyString (), mockInstance.method (), when (), thenReturn, tất cả chúng đều có 'tác dụng phụ' đặc biệt. Đó cũng là lý do tại sao cái nhìn đầu tiên của framework trông giống như một phép thuật - chúng tôi thường không viết mã như vậy. Tuy nhiên, trong trường hợp khuôn khổ chế nhạo, thiết kế chống mẫu này là một thiết kế tuyệt vời, vì nó dẫn đến API rất đơn giản.


4
Liên kết xuất sắc. Thiên tài đằng sau điều này là: API rất đơn giản khiến toàn bộ mọi thứ trông rất đẹp. Một quyết định tuyệt vời khác là phương thức when () sử dụng generic để phương thức thenReturn () là kiểu an toàn.
David Tonhofer

Tôi coi đây là câu trả lời tốt hơn. Ngược lại với câu trả lời khác, nó giải thích rõ ràng các khái niệm về quy trình mô phỏng thay vì luồng điều khiển thông qua mã cụ thể. Tôi đồng ý, liên kết tuyệt vời.
mihca
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.