이것이 Mockito의 재설정 방법을 적절하게 사용합니까?


68

테스트 클래스에는 일반적으로 사용되는 Bar객체 를 구성하는 개인 메서드가 있습니다. Bar생성자는 호출 someMethod()내 조롱 개체의 방법 :

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

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

내 테스트 방법 중 일부에서 someMethod특정 테스트에 의해 호출 되기를 원했습니다 . 다음과 같은 것 :

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

  // do some things

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

조롱 된 객체가 someMethod두 번 호출 되었으므로 실패합니다 . 테스트 메소드가 메소드의 부작용을 신경 쓰지 않기를 원 getBar()하므로 끝에 모의 객체를 재설정하는 것이 합리적 getBar()입니까?

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

문서에 모의 객체를 재설정하는 것이 일반적으로 나쁜 테스트를 나타 내기 때문에 제안합니다. 그러나 이것은 나에게 괜찮습니다.

대안

다른 선택은 전화하는 것 같습니다 :

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

내 의견으로는 각 테스트가 기대치에 대해 알도록 강요 getBar()하지 않습니다.

답변:


60

나는 이것이 사용이 괜찮은 경우 중 하나라고 생각합니다 reset(). 작성중인 테스트는 "일부"가에 대한 단일 호출을 트리거하는지 테스트하고 someMethod()있습니다. verify()호출 횟수가 다른 명령문을 작성하면 혼동 될 수 있습니다.

  • atLeastOnce() 오 탐지를 허용하므로 테스트가 항상 정확하기를 원하기 때문에 나쁜 것입니다.
  • times(2)가양 성을 방지하지만 "제작자가 하나를 추가한다는 것을 알고 있습니다"라는 말보다는 두 번의 호출을 기대하는 것처럼 보입니다. 또한 생성자에서 추가 호출을 추가하기 위해 무언가가 변경되면 테스트에서 오 탐지 가능성이 있습니다. 또한 테스트 대상이 아닌 테스트가 잘못 되었기 때문에 호출을 제거하면 테스트가 실패합니다.

사용하여 reset()헬퍼 방법, 당신은 이러한 문제를 모두 피할 수 있습니다. 그러나 수행 한 스터 빙도 재설정되므로주의해야합니다. reset()권장하지 않는 주요 이유 는

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

이것은 OP가 시도하는 것이 아닙니다. 내가 생각하는 OP에는 생성자의 호출을 확인하는 테스트가 있습니다. 이 테스트의 경우 재설정을 통해이 단일 조치를 분리 할 수 ​​있으며 효과가 있습니다. 이 몇 가지 경우 중 하나 reset()가 도움이 될 수 있습니다. 그것을 사용하지 않는 다른 옵션에는 단점이 있습니다. OP 가이 게시물을 만들었다는 사실은 그가 재설정 방법을 맹목적으로 사용하는 것이 아니라 상황에 대해 생각하고 있음을 보여줍니다.


17
Mockito가 resetInteractions () 호출을 제공하여 verify (..., times (...))의 목적으로 과거의 상호 작용을 잊고 스터 빙을 유지하기를 바랍니다. 이것은 {setup; 행위; 확인;} 처리하기가 훨씬 쉽습니다. {setup; resetInteractions; 행위; verify}
Arkadiy

2
실제로 Mockito 2.1 이후에는 스텁을 재설정하지 않고 호출을 지우는 방법을 제공합니다.Mockito.clearInvocations(T... mocks)
Colin D Bennett

6

Smart Mockito 사용자는 재설정 기능을 거의 사용하지 않습니다. 테스트 기능이 불량하다는 표시 일 수 있기 때문입니다. 일반적으로 모의를 재설정 할 필요가 없으며 각 테스트 방법마다 새 모의를 작성하십시오.

대신에 reset(), 간단한 작은 쓰기 고려하고 긴, 지정된 오버 테스트를 통해 시험 방법을 집중하십시오. 첫 번째 잠재적 코드 냄새는 reset()테스트 방법의 중간에 있습니다.

mockito 문서 에서 추출되었습니다 .

내 조언은 당신이 사용하지 않도록 노력하는 것 reset()입니다. 제 생각에는 someMethod를 두 번 호출하면 테스트해야합니다 (데이터베이스 액세스 또는 처리하려는 다른 긴 프로세스 일 수 있습니다).

정말로 신경 쓰지 않는다면 다음을 사용할 수 있습니다.

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

getBar에서 someMethod를 호출하고 이후가 아니라 호출하면 마지막으로 잘못된 결과가 발생할 수 있습니다 (잘못된 동작이지만 테스트는 실패하지 않음).


2
예, 나는 그 정확한 인용문을 보았습니다 (제 질문과 관련이 있습니다). 현재 위의 예가 왜 "나쁜"지에 대한 적절한 논쟁을 아직 보지 못했습니다. 당신은 하나를 공급할 수 있습니까?
던컨 존스

모의 객체를 재설정 해야하는 경우 테스트에서 너무 많은 것을 테스트하려고하는 것 같습니다. 작은 것을 테스트하면서 두 가지 테스트로 나눌 수 있습니다. 어쨌든 getBar 메서드 내부를 확인하는 이유를 모르겠습니다. 테스트중인 것을 추적하기가 어렵습니다. 클래스가해야 할 일 (최소한 두 번, 적어도 한 번, 한 번만, 절대 등)을 호출해야하는 경우 테스트 사고를 설계하고 모든 검증을 동일한 장소에서 수행하는 것이 좋습니다.
greuze

verify개인 메서드를 호출하지 않아도 문제가 지속된다는 것을 강조하기 위해 질문을 편집했습니다 (동의합니다, 아마도 거기에 속하지 않을 것입니다). 답변이 변경 될지 여부에 대한 귀하의 의견을 환영합니다.
Duncan Jones

재설정을 사용해야하는 좋은 이유가 많이 있습니다.이 경우 그 mockito 인용에 너무주의를 기울이지 않을 것입니다. 테스트 스위트를 실행할 때 Spring의 JUnit 클래스 러너를 사용하면 원치 않는 상호 작용이 발생할 수 있습니다. 특히 모의 데이터베이스 호출 또는 리플렉션을 사용하지 않는 개인 메소드가 포함 된 호출을 포함하는 테스트를 수행하는 경우 특히 그렇습니다.
Sandy Simonton

나는 여러 가지를 테스트하고 싶을 때 일반적으로 어려운 일이지만 JUnit은 테스트를 매개 변수화하는 좋은 방법을 제공하지 않습니다. NUnit과 달리 예를 들어 주석이 있습니다.
Stefan Hendriks

3

절대적으로하지. 항상 그렇듯이, 깨끗한 테스트를 작성하는 데 어려움이 있다면 프로덕션 코드 디자인에 대한 주요 위험 신호입니다. 이 경우 가장 좋은 해결책은 Bar 생성자가 메서드를 호출하지 않도록 코드를 리팩터링하는 것입니다.

생성자는 논리를 실행하지 말고 구성해야합니다. 메소드의 리턴 값을 가져 와서 생성자 매개 변수로 전달하십시오.

new Bar(mockedObject);

된다 :

new Bar(mockedObject.someMethod());

이로 인해 여러 장소에서이 논리가 복제되는 경우 Bar 객체와 독립적으로 테스트 할 수있는 팩토리 메소드를 만드는 것이 좋습니다.

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

이 리팩토링이 너무 어려운 경우 reset ()을 사용하는 것이 좋습니다. 그러나 분명히하자-코드가 제대로 설계되지 않았 음을 나타냅니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.