Đây có phải là cách sử dụng thích hợp của phương pháp thiết lập lại của Mockito không?


68

Tôi có một phương thức riêng trong lớp thử nghiệm của mình để xây dựng một Barđối tượng thường được sử dụng . Hàm Bartạo gọi someMethod()phương thức trong đối tượng giả định của tôi:

private @Mock Foo mockedObject; // My mocked object
...

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
}

Trong một số phương pháp thử nghiệm của tôi, tôi muốn kiểm tra someMethodcũng được gọi bằng thử nghiệm cụ thể đó. Một cái gì đó như sau:

@Test
public void someTest() {
  Bar bar = getBar();

  // do some things

  verify(mockedObject).someMethod(); // <--- will fail
}

Điều này thất bại, bởi vì đối tượng bị chế giễu đã someMethodgọi hai lần. Tôi không muốn các phương thức kiểm tra của mình quan tâm đến các tác dụng phụ của getBar()phương pháp của mình , vậy liệu có hợp lý khi đặt lại đối tượng giả của tôi vào cuối getBar()không?

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
  reset(mockedObject); // <-- is this OK?
}

Tôi hỏi, bởi vì tài liệu cho thấy việc đặt lại các đối tượng giả thường là dấu hiệu của các xét nghiệm xấu. Tuy nhiên, điều này cảm thấy ổn với tôi.

Thay thế

Sự lựa chọn thay thế dường như đang gọi:

verify(mockedObject, times(2)).someMethod();

mà theo tôi buộc mỗi bài kiểm tra phải biết về những kỳ vọng getBar(), không có lợi.

Câu trả lời:


60

Tôi tin rằng đây là một trong những trường hợp sử dụng reset()là ok. Bài kiểm tra bạn đang viết đang kiểm tra rằng "một số thứ" kích hoạt một cuộc gọi đến someMethod(). Viết verify()tuyên bố với bất kỳ số lượng yêu cầu khác nhau có thể dẫn đến nhầm lẫn.

  • atLeastOnce() cho phép dương tính giả, đó là một điều xấu khi bạn muốn các bài kiểm tra của mình luôn đúng.
  • times(2)ngăn chặn dương tính giả, nhưng làm cho có vẻ như bạn đang mong đợi hai lệnh thay vì nói "tôi biết nhà xây dựng thêm một". Hơn nữa, nếu một cái gì đó thay đổi trong hàm tạo để thêm một cuộc gọi bổ sung, thử nghiệm bây giờ có cơ hội cho một dương tính giả. Và loại bỏ cuộc gọi sẽ khiến thử nghiệm thất bại vì thử nghiệm hiện đang sai thay vì những gì đang được thử nghiệm là sai.

Bằng cách sử dụng reset()trong phương thức trợ giúp, bạn tránh được cả hai vấn đề này. Tuy nhiên, bạn cần phải cẩn thận rằng nó cũng sẽ thiết lập lại bất kỳ sơ khai nào bạn đã thực hiện, vì vậy hãy cảnh báo. Lý do chính reset()được khuyến khích là để ngăn chặn

bar = mock(Bar.class);
//do stuff
verify(bar).someMethod();
reset(bar);
//do other stuff
verify(bar).someMethod2();

Đây không phải là những gì OP đang cố gắng làm. OP, tôi giả sử, có một bài kiểm tra xác minh lời gọi trong hàm tạo. Đối với thử nghiệm này, thiết lập lại cho phép cô lập hành động đơn lẻ này và nó có hiệu lực. Đây là một trong số ít trường hợp reset()có thể hữu ích như. Các tùy chọn khác không sử dụng đều có nhược điểm. Việc OP thực hiện bài đăng này cho thấy anh ta đang suy nghĩ về tình huống và không chỉ sử dụng phương pháp thiết lập lại một cách mù quáng.


17
Tôi muốn Mockito cung cấp lệnh gọi resetInterilities () để quên các tương tác trong quá khứ với mục đích xác minh (..., lần (...)) và giữ nguyên gốc. Điều này sẽ làm cho các tình huống thử nghiệm của {setup; hành động; xác minh;} rằng dễ dàng hơn để đối phó với. Nó sẽ là {thiết lập; thiết lập lại; hành động; xác minh}
Arkadiy

2
Trên thực tế kể từ Mockito 2.1, nó cung cấp một cách để xóa các yêu cầu mà không cần thiết lập lại các sơ khai:Mockito.clearInvocations(T... mocks)
Colin D Bennett

6

Người dùng Mockito thông minh hầu như không sử dụng tính năng thiết lập lại vì họ biết đó có thể là dấu hiệu của các bài kiểm tra kém. Thông thường, bạn không cần thiết lập lại các bản giả của mình, chỉ cần tạo các bản giả mới cho mỗi phương pháp kiểm tra.

Thay vì reset()vui lòng xem xét việc viết các phương pháp kiểm tra đơn giản, nhỏ và tập trung qua các bài kiểm tra dài, quá chỉ định. Mùi mã tiềm năng đầu tiên là reset()ở giữa phương pháp thử nghiệm.

Trích từ các tài liệu mockito .

Lời khuyên của tôi là bạn cố gắng tránh sử dụng reset(). Theo tôi, nếu bạn gọi hai lần đến someMethod, thì điều đó nên được kiểm tra (có thể đó là quyền truy cập cơ sở dữ liệu hoặc quá trình dài khác mà bạn muốn quan tâm).

Nếu bạn thực sự không quan tâm đến điều đó, bạn có thể sử dụng:

verify(mockedObject, atLeastOnce()).someMethod();

Lưu ý rằng điều này cuối cùng có thể gây ra kết quả sai, nếu bạn gọi someMethod từ getBar và không phải sau (đây là hành vi sai, nhưng thử nghiệm sẽ không thất bại).


2
Vâng, tôi đã thấy trích dẫn chính xác đó (tôi đã liên kết với nó từ câu hỏi của tôi). Hiện tại tôi vẫn chưa thấy một lập luận đúng đắn về lý do tại sao ví dụ của tôi ở trên là "xấu". Bạn có thể cung cấp một?
Duncan Jones

Nếu bạn cần thiết lập lại các đối tượng giả, có vẻ như bạn đang cố kiểm tra quá nhiều thứ trong bài kiểm tra của mình. Bạn có thể chia nó thành hai bài kiểm tra, kiểm tra những thứ nhỏ hơn. Trong mọi trường hợp, tôi không biết tại sao bạn lại xác minh bên trong phương thức getBar, thật khó để theo dõi những gì bạn đang thử nghiệm. Tôi khuyên bạn nên thiết kế suy nghĩ kiểm tra của mình theo những gì lớp bạn nên làm (nếu bạn phải gọi someMethod chính xác hai lần, ít nhất một lần, chỉ một lần, không bao giờ, v.v.) và thực hiện mọi xác minh ở cùng một nơi.
chào mừng

Tôi đã chỉnh sửa câu hỏi của mình để nhấn mạnh rằng vấn đề vẫn tồn tại ngay cả khi tôi không gọi verifytheo phương thức riêng tư của mình (mà tôi đồng ý, có lẽ không thuộc về nơi đó). Tôi hoan nghênh ý kiến ​​của bạn về việc câu trả lời của bạn sẽ thay đổi.
Duncan Jones

Có rất nhiều lý do tốt để sử dụng thiết lập lại, tôi sẽ không chú ý quá nhiều trong trường hợp này đối với trích dẫn mockito đó. Bạn có thể có Trình chạy lớp JUnit của Spring khi chạy bộ kiểm tra gây ra các tương tác không mong muốn, đặc biệt nếu bạn đang thực hiện kiểm tra liên quan đến các cuộc gọi cơ sở dữ liệu hoặc các cuộc gọi liên quan đến các phương thức riêng tư mà bạn không quan tâm để sử dụng phản xạ.
Sandy Simonton

Tôi thường thấy đây là một điều khó khăn khi tôi muốn kiểm tra nhiều thứ, nhưng JUnit đơn giản là không cung cấp bất kỳ cách (!) Đẹp nào để tham số hóa các bài kiểm tra. Không giống như NUnit làm ví dụ với các chú thích.
Stefan Hendriks

3

Tuyệt đối không. Như thường lệ, khó khăn khi bạn viết một bài kiểm tra sạch là một lá cờ đỏ lớn về thiết kế mã sản xuất của bạn. Trong trường hợp này, giải pháp tốt nhất là cấu trúc lại mã của bạn để hàm tạo của Bar không gọi bất kỳ phương thức nào.

Các nhà xây dựng nên xây dựng, không thực hiện logic. Lấy giá trị trả về của phương thức và truyền vào dưới dạng tham số hàm tạo.

new Bar(mockedObject);

trở thành:

new Bar(mockedObject.someMethod());

Nếu điều này sẽ dẫn đến việc sao chép logic này ở nhiều nơi, hãy xem xét việc tạo một phương thức xuất xưởng có thể được kiểm tra độc lập với đối tượng Bar của bạn:

public Bar createBar(MockedObject mockedObject) {
    Object dependency = mockedObject.someMethod();
    // ...more logic that used to be in Bar constructor
    return new Bar(dependency);
}

Nếu việc tái cấu trúc này quá khó khăn thì sử dụng reset () là một công việc tốt. Nhưng hãy rõ ràng - điều đó cho thấy rằng mã của bạn được thiết kế kém.

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.