Khi nào nên sử dụng Mockito.verify ()?


201

Tôi viết trường hợp kiểm tra jUnit cho 3 mục đích:

  1. Để đảm bảo rằng mã của tôi đáp ứng tất cả các chức năng được yêu cầu, trong tất cả (hoặc hầu hết) các kết hợp / giá trị đầu vào.
  2. Để đảm bảo rằng tôi có thể thay đổi việc triển khai và dựa vào các trường hợp kiểm tra JUnit để cho tôi biết rằng tất cả chức năng của tôi vẫn hài lòng.
  3. Là một tài liệu về tất cả các trường hợp sử dụng, mã của tôi xử lý và hoạt động như một đặc tả để tái cấu trúc - nếu mã cần phải được viết lại. (Tái cấu trúc mã và nếu các bài kiểm tra jUnit của tôi không thành công - bạn có thể đã bỏ lỡ một số trường hợp sử dụng).

Tôi không hiểu tại sao hoặc khi nào Mockito.verify()nên được sử dụng. Khi tôi thấy verify()được gọi, nó nói với tôi rằng jUnit của tôi đang trở nên ý thức về việc thực hiện. (Do đó, việc thay đổi triển khai của tôi sẽ phá vỡ jUnits của tôi, mặc dù chức năng của tôi không bị ảnh hưởng).

Tôi đang tìm kiếm:

  1. Điều gì nên là hướng dẫn cho việc sử dụng thích hợp Mockito.verify()?

  2. Điều này về cơ bản có đúng không khi jUnits nhận thức được, hoặc kết hợp chặt chẽ với việc triển khai lớp đang thử nghiệm?


1
Tôi cố gắng tránh sử dụng verify () nhiều nhất có thể, vì lý do tương tự như bạn đã phơi bày (tôi không muốn thử nghiệm đơn vị của mình nhận thức được việc triển khai), nhưng có một trường hợp khi tôi không có lựa chọn nào khác - khai thác các phương thức void. Nói chung, vì họ không trả lại bất cứ thứ gì họ không đóng góp cho đầu ra 'thực tế' của bạn; nhưng vẫn cần phải biết rằng nó đã được gọi. Nhưng tôi đồng ý với bạn, thật vô nghĩa khi sử dụng xác minh để xác minh luồng thực thi.
Legna

Câu trả lời:


78

Nếu hợp đồng của lớp A bao gồm thực tế là nó gọi phương thức B của một đối tượng loại C, thì bạn nên kiểm tra điều này bằng cách tạo một mô hình loại C và xác minh rằng phương thức B đã được gọi.

Điều này ngụ ý rằng hợp đồng của lớp A có đủ chi tiết mà nó nói về loại C (có thể là giao diện hoặc lớp). Vì vậy, có, chúng ta đang nói về một mức độ đặc tả vượt ra ngoài "yêu cầu hệ thống" và đi theo một cách nào đó để mô tả việc thực hiện.

Điều này là bình thường đối với các bài kiểm tra đơn vị. Khi bạn đang kiểm tra đơn vị, bạn muốn đảm bảo rằng mỗi đơn vị đang thực hiện "điều đúng" và điều đó thường sẽ bao gồm các tương tác của đơn vị đó với các đơn vị khác. "Đơn vị" ở đây có thể có nghĩa là các lớp hoặc tập hợp con lớn hơn của ứng dụng của bạn.

Cập nhật:

Tôi cảm thấy rằng điều này không chỉ áp dụng cho xác minh, mà còn cả sơ khai. Ngay khi bạn khai thác một phương thức của lớp cộng tác viên, bài kiểm tra đơn vị của bạn đã trở thành, theo một cách nào đó, phụ thuộc vào việc thực hiện. Đó là bản chất của các bài kiểm tra đơn vị là như vậy. Vì Mockito cũng nhiều về việc khai thác cũng như về xác minh, nên thực tế là bạn đang sử dụng Mockito ngụ ý rằng bạn sẽ chạy qua loại phụ thuộc này.

Theo kinh nghiệm của tôi, nếu tôi thay đổi việc thực hiện một lớp, tôi thường phải thay đổi việc thực hiện các bài kiểm tra đơn vị của nó để phù hợp. Tuy nhiên, thông thường, tôi sẽ không phải thay đổi kho lưu trữ các bài kiểm tra đơn vị nào cho lớp; tất nhiên trừ khi, lý do cho sự thay đổi là sự tồn tại của một điều kiện mà tôi đã không kiểm tra trước đó.

Vì vậy, đây là những gì bài kiểm tra đơn vị là về. Một thử nghiệm không bị loại phụ thuộc này vào cách sử dụng các lớp cộng tác viên thực sự là một thử nghiệm hệ thống phụ hoặc thử nghiệm tích hợp. Tất nhiên, những điều này cũng thường được viết bằng JUnit và thường liên quan đến việc sử dụng chế độ chế nhạo. Theo tôi, "JUnit" là một cái tên khủng khiếp, cho một sản phẩm cho phép chúng tôi sản xuất tất cả các loại thử nghiệm khác nhau.


8
Cảm ơn, David. Sau khi quét qua một số bộ mã, đây có vẻ như là một cách thông thường - nhưng đối với tôi, điều này đánh bại mục đích tạo ra các thử nghiệm đơn vị và chỉ thêm chi phí duy trì chúng cho rất ít giá trị. Tôi hiểu lý do tại sao các giả được yêu cầu và tại sao các phụ thuộc để thực hiện kiểm tra cần phải được thiết lập. Nhưng theo tôi, việc xác minh rằng phụ thuộc phương thứcA.XYZ () được thực thi làm cho các bài kiểm tra rất dễ vỡ, theo ý kiến ​​của tôi.
Russell

@Russell Ngay cả khi "loại C" là giao diện cho trình bao bọc xung quanh thư viện hoặc xung quanh một số hệ thống con riêng biệt của ứng dụng của bạn?
Dawood ibn Kareem

1
Tôi sẽ không nói rằng nó hoàn toàn vô dụng để đảm bảo một số hệ thống hoặc dịch vụ phụ được yêu cầu - chỉ cần có một số hướng dẫn xung quanh nó (xây dựng chúng là điều tôi muốn làm). Ví dụ: (Tôi có thể chỉ đơn giản là quá đơn giản) Giả sử, tôi đang sử dụng StrUtil.equals () trong mã của mình và quyết định chuyển sang StrUtil.equalsIgnoreCase () trong quá trình triển khai. ), thử nghiệm của tôi có thể thất bại mặc dù việc thực hiện là chính xác. Cuộc gọi xác minh này, IMO, là thông lệ xấu mặc dù nó dành cho các thư viện / hệ thống phụ. Mặt khác, sử dụng xác minh để đảm bảo lệnh gọi tới closeDbConn có thể là một usecase hợp lệ.
Russell

1
Tôi hiểu bạn và đồng ý với bạn hoàn toàn. Nhưng tôi cũng cảm thấy rằng việc viết các hướng dẫn mà bạn mô tả có thể mở rộng thành việc viết toàn bộ sách giáo khoa TDD hoặc BDD. Lấy ví dụ của bạn, gọi equals()hoặc equalsIgnoreCase()sẽ không bao giờ là một cái gì đó được chỉ định trong các yêu cầu của một lớp, do đó sẽ không bao giờ có một bài kiểm tra đơn vị mỗi se. Tuy nhiên, "đóng kết nối DB khi hoàn thành" (bất kể điều này có nghĩa là gì về mặt triển khai) cũng có thể là một yêu cầu của một lớp, mặc dù đó không phải là "yêu cầu kinh doanh". Đối với tôi, điều này xuất phát từ mối quan hệ giữa hợp đồng ...
Dawood ibn Kareem

... của một lớp như thể hiện trong các yêu cầu nghiệp vụ của nó và tập hợp các phương thức kiểm tra mà đơn vị kiểm tra lớp đó. Xác định mối quan hệ này sẽ là một chủ đề quan trọng trong bất kỳ cuốn sách nào về TDD hoặc BDD. Trong khi ai đó trong nhóm Mockito có thể viết một bài về chủ đề này cho wiki của họ, tôi không thấy nó khác với nhiều tài liệu có sẵn khác như thế nào. Nếu bạn thấy nó có thể khác nhau như thế nào, hãy cho tôi biết, và có lẽ chúng ta có thể làm việc với nhau.
Dawood ibn Kareem

60

Câu trả lời của David tất nhiên là đúng nhưng không hoàn toàn giải thích lý do tại sao bạn muốn điều này.

Về cơ bản, khi kiểm tra đơn vị, bạn đang kiểm tra một đơn vị chức năng trong sự cô lập. Bạn kiểm tra xem đầu vào tạo ra đầu ra dự kiến. Đôi khi, bạn phải kiểm tra tác dụng phụ là tốt. Tóm lại, xác minh cho phép bạn làm điều đó.

Ví dụ, bạn có một chút logic kinh doanh được cho là lưu trữ mọi thứ bằng cách sử dụng DAO. Bạn có thể thực hiện việc này bằng cách sử dụng kiểm tra tích hợp để khởi tạo DAO, nối nó với logic nghiệp vụ và sau đó chọc vào cơ sở dữ liệu để xem liệu các công cụ dự kiến ​​có được lưu trữ không. Đó không phải là một bài kiểm tra đơn vị nữa.

Hoặc, bạn có thể chế nhạo DAO và xác minh rằng nó được gọi theo cách bạn mong đợi. Với mockito, bạn có thể xác minh rằng một cái gì đó được gọi, tần suất được gọi và thậm chí sử dụng các công cụ đối sánh trên các tham số để đảm bảo nó được gọi theo một cách cụ thể.

Mặt trái của kiểm thử đơn vị như thế này thực sự là bạn đang buộc các bài kiểm tra vào việc thực hiện khiến việc tái cấu trúc khó hơn một chút. Mặt khác, một mùi thiết kế tốt là số lượng mã cần thiết để thực hiện nó đúng cách. Nếu các bài kiểm tra của bạn cần phải rất dài, có lẽ có gì đó không ổn với thiết kế. Vì vậy, mã với rất nhiều tác dụng phụ / tương tác phức tạp cần được kiểm tra có lẽ không phải là một điều tốt để có.


29

Đây là câu hỏi tuyệt vời! Tôi nghĩ nguyên nhân sâu xa của nó là do sau đây, chúng tôi đang sử dụng JUnit không chỉ để thử nghiệm đơn vị. Vì vậy, câu hỏi nên được chia ra:

  • Tôi có nên sử dụng Mockito.verify () trong tích hợp (hoặc bất kỳ thử nghiệm cao hơn đơn vị nào khác) không?
  • Tôi có nên sử dụng Mockito.verify () trong thử nghiệm đơn vị hộp đen của mình không?
  • Tôi có nên sử dụng Mockito.verify () trong thử nghiệm đơn vị hộp trắng của mình không?

vì vậy nếu chúng ta bỏ qua kiểm tra đơn vị cao hơn, câu hỏi có thể được đặt lại " Sử dụng kiểm tra đơn vị hộp trắng với Mockito.verify () tạo ra cặp đôi tuyệt vời giữa kiểm tra đơn vị và triển khai có thể của tôi, tôi có thể tạo một số " hộp màu xám không " kiểm tra đơn vị và tôi nên sử dụng quy tắc nào cho việc này ".

Bây giờ, chúng ta hãy đi qua tất cả các bước này.

* - Tôi có nên sử dụng Mockito.verify () trong tích hợp (hoặc bất kỳ thử nghiệm cao hơn đơn vị nào khác) không? * Tôi nghĩ rằng câu trả lời rõ ràng là không, hơn nữa, bạn không nên sử dụng giả cho việc này. Bài kiểm tra của bạn phải càng gần với ứng dụng thực càng tốt. Bạn đang kiểm tra trường hợp sử dụng hoàn chỉnh, không phải là một phần của ứng dụng.

* kiểm tra đơn vị hộp đen so với kiểm thử đơn vị hộp trắng * Nếu bạn đang sử dụng phương pháp tiếp cận hộp đen bạn thực sự đang làm gì, bạn cung cấp (tất cả các lớp tương đương) đầu vào, trạng thái và kiểm tra rằng bạn sẽ nhận được đầu ra mong đợi. Theo cách tiếp cận này, việc sử dụng giả nói chung là hợp lý (bạn chỉ bắt chước rằng họ đang làm đúng; bạn không muốn kiểm tra chúng), nhưng gọi Mockito.verify () là không cần thiết.

Nếu bạn đang sử dụng phương pháp hộp trắng, bạn thực sự đang làm gì, bạn đang kiểm tra hành vi của đơn vị mình. Trong phương pháp này, việc gọi tới Mockito.verify () là điều cần thiết, bạn nên xác minh rằng đơn vị của bạn hoạt động như bạn mong đợi.

quy tắc ngón tay cái để kiểm tra hộp xám Vấn đề với kiểm thử hộp trắng là nó tạo ra sự khớp nối cao. Một giải pháp khả thi là thực hiện kiểm tra hộp xám, không kiểm thử hộp trắng. Đây là loại kết hợp kiểm tra hộp đen trắng. Bạn đang thực sự kiểm tra hành vi của đơn vị của mình như trong thử nghiệm hộp trắng, nhưng nói chung, bạn làm cho nó không thực hiện khi có thể . Khi có thể, bạn sẽ chỉ thực hiện một kiểm tra như trong trường hợp hộp đen, chỉ cần khẳng định rằng đầu ra là những gì bạn mong đợi. Vì vậy, bản chất của câu hỏi của bạn là khi có thể.

Điều này thực sự khó khăn. Tôi không có một ví dụ hay, nhưng tôi có thể cho bạn ví dụ. Trong trường hợp đã được đề cập ở trên với equals () vs equalsIgnoreCase (), bạn không nên gọi Mockito.verify (), chỉ cần xác nhận đầu ra. Nếu bạn không thể làm điều đó, hãy chia mã của bạn thành đơn vị nhỏ hơn, cho đến khi bạn có thể làm điều đó. Mặt khác, giả sử bạn có một số @Service và bạn đang viết @ Web-Service về cơ bản là trình bao bọc cho @Service của bạn - nó ủy thác tất cả các cuộc gọi đến @Service (và thực hiện một số xử lý lỗi bổ sung). Trong trường hợp này, việc gọi tới Mockito.verify () là điều cần thiết, bạn không nên sao chép tất cả các kiểm tra mà bạn đã thực hiện cho @Serive, xác minh rằng bạn đang gọi tới @Service với danh sách tham số chính xác là đủ.


Kiểm tra hộp màu xám là một chút của một cạm bẫy. Tôi có xu hướng hạn chế nó vào những thứ như DAO. Tôi đã tham gia vào một số dự án với các bản dựng cực kỳ chậm vì có rất nhiều bài kiểm tra hộp màu xám, thiếu hoàn toàn các bài kiểm tra đơn vị và quá nhiều bài kiểm tra hộp đen để bù đắp cho sự thiếu tin tưởng vào những bài kiểm tra greybox được cho là kiểm tra.
Jilles van Gurp

Đối với tôi đây là câu trả lời tốt nhất vì nó trả lời khi nào nên sử dụng Mockito.when () trong nhiều tình huống. Làm tốt.
Michiel Leegwater

8

Tôi phải nói rằng, bạn hoàn toàn đúng theo quan điểm của một cách tiếp cận cổ điển:

  • Nếu trước tiên bạn tạo (hoặc thay đổi) logic nghiệp vụ cho ứng dụng của mình và sau đó bao phủ nó bằng (kiểm tra) ( phương pháp Kiểm tra lần cuối ), thì sẽ rất đau đớn và nguy hiểm khi cho phép kiểm tra biết bất cứ điều gì về cách phần mềm của bạn hoạt động, ngoài việc kiểm tra đầu vào và đầu ra.
  • Nếu bạn đang thực hành phương pháp Kiểm tra theo hướng , thì các bài kiểm tra của bạn là lần đầu tiên được viết, được thay đổi và phản ánh các trường hợp sử dụng chức năng của phần mềm. Việc thực hiện phụ thuộc vào các bài kiểm tra. Điều đó đôi khi có nghĩa là, bạn muốn phần mềm của mình được triển khai theo một cách cụ thể nào đó, ví dụ như dựa vào phương pháp của một số thành phần khác hoặc thậm chí gọi nó là một số lần cụ thể. Đó là nơi Mockito.verify () có ích!

Điều quan trọng cần nhớ là không có công cụ phổ quát. Loại phần mềm, kích thước, mục tiêu của công ty và tình hình thị trường, kỹ năng nhóm và nhiều thứ khác ảnh hưởng đến quyết định sử dụng phương pháp nào trong trường hợp cụ thể của bạn.


0

Như một số người nói

  1. Đôi khi bạn không có đầu ra trực tiếp mà bạn có thể khẳng định
  2. Đôi khi bạn chỉ cần xác nhận rằng phương thức được thử nghiệm của bạn đang gửi các đầu ra gián tiếp chính xác đến các cộng tác viên của nó (mà bạn đang chế giễu).

Về mối quan tâm của bạn về việc phá vỡ các bài kiểm tra của bạn khi tái cấu trúc, điều đó có phần được mong đợi khi sử dụng giả / cuống / gián điệp. Tôi có nghĩa là theo định nghĩa và không liên quan đến việc thực hiện cụ thể như Mockito. Nhưng bạn có thể nghĩ theo cách này - nếu bạn cần thực hiện tái cấu trúc sẽ tạo ra những thay đổi lớn trên cách thức phương pháp của bạn hoạt động, thì nên thực hiện theo phương pháp TDD, nghĩa là trước tiên bạn có thể thay đổi thử nghiệm của mình để xác định hành vi mới (sẽ thất bại trong bài kiểm tra), sau đó thực hiện các thay đổi và kiểm tra lại được thông qua.


0

Trong hầu hết các trường hợp khi mọi người không thích sử dụng Mockito.verify, đó là vì nó được sử dụng để xác minh mọi thứ mà đơn vị được kiểm tra đang thực hiện và điều đó có nghĩa là bạn sẽ cần điều chỉnh thử nghiệm của mình nếu có bất kỳ thay đổi nào trong đó. Nhưng, tôi không nghĩ đó là một vấn đề. Nếu bạn muốn có thể thay đổi phương thức làm gì mà không cần thay đổi thử nghiệm, điều đó có nghĩa là bạn muốn viết thử nghiệm mà không kiểm tra mọi thứ mà phương pháp của bạn đang thực hiện, vì bạn không muốn thử nghiệm thay đổi của mình . Và đó là cách nghĩ sai lầm.

Điều thực sự là một vấn đề, là nếu bạn có thể sửa đổi những gì phương pháp của bạn làm và một bài kiểm tra đơn vị được cho là bao gồm các chức năng hoàn toàn không thất bại. Điều đó có nghĩa là bất kể ý định thay đổi của bạn là gì, kết quả của sự thay đổi của bạn không nằm trong thử nghiệm.

Do đó, tôi thích giả tạo càng nhiều càng tốt: cũng chế giễu các đối tượng dữ liệu của bạn. Khi làm điều đó, bạn không chỉ có thể sử dụng xác minh để kiểm tra xem các phương thức chính xác của các lớp khác có được gọi hay không mà dữ liệu được truyền qua được thu thập thông qua các phương thức chính xác của các đối tượng dữ liệu đó. Và để làm cho nó hoàn thành, bạn nên kiểm tra thứ tự các cuộc gọi xảy ra. Ví dụ: nếu bạn sửa đổi một đối tượng thực thể db và sau đó lưu nó bằng kho lưu trữ, thì việc xác minh rằng các setters của đối tượng được gọi với dữ liệu chính xác là không đủ và phương thức lưu trữ của kho được gọi. Nếu chúng được gọi sai thứ tự, phương thức của bạn vẫn không làm những gì nó nên làm. Vì vậy, tôi không sử dụng Mockito.verify nhưng tôi tạo một đối tượng inOrder với tất cả các giả và thay vào đó sử dụng inOrder.verify. Và nếu bạn muốn làm cho nó hoàn chỉnh, bạn cũng nên gọi Mockito. xác minh Không có các giao dịch ở cuối và vượt qua tất cả các giả. Nếu không, ai đó có thể thêm chức năng / hành vi mới mà không cần kiểm tra, điều đó có nghĩa là sau khi thống kê bảo hiểm của bạn có thể là 100% và bạn vẫn chồng chất mã không được xác nhận hoặc xác minh.

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.