mockito를 사용한 Private 메서드 테스트


104
공개 클래스 A {

    public void method (boolean b) {
          if (b == true)
               method1 ();
          그밖에
               method2 ();
    }

    private void method1 () {}
    private void method2 () {}
}
public class TestA {

    @테스트
    public void testMethod () {
      A a = mock (A.class);
      a.method (true);
      // verify (a) .method1 ();과 같이 테스트하는 방법
    }
}

비공개 메소드를 테스트하는 방법은 호출 여부와 mockito를 사용하여 비공개 메소드를 테스트하는 방법은 무엇입니까 ???


답변:


81

Mockito로는 그렇게 할 수 없지만 Powermock 을 사용하여 Mockito 를 확장하고 개인 메서드를 모의 할 수 있습니다 . Powermock은 Mockito를 지원합니다. 여기 에 예가 있습니다.


19
이 답변과 혼동됩니다. 이것은 조롱입니다. 그러나 제목은 비공개 방법을 테스트하고 있습니다
diyoda_

나는 Powermock을 사용하여 private 메서드를 모의했지만 Powermock으로 private 메서드를 어떻게 테스트 할 수 있습니까? 어디에서 입력을 전달하고 메서드에서 출력을 기대 한 다음 출력을 확인할 수 있습니까?
Rito

입력 출력을 조롱하면 실제 기능을 테스트 할 수 없습니다.
Talha

131

mockito를 통해서는 불가능합니다. 자신의에서 위키

Mockito가 개인 메서드를 조롱하지 않는 이유는 무엇입니까?

첫째, 우리는 사적인 방법을 조롱하는 것에 대해 독단적이지 않습니다. 우리는 private 메서드를 테스트하는 관점에서 볼 때 private 메서드가 존재하지 않기 때문에 신경 쓰지 않습니다. Mockito가 개인 메서드를 모의하지 않는 몇 가지 이유는 다음과 같습니다.

방탄이 아닌 클래스 로더를 해킹해야하며 API를 변경해야합니다 (사용자 지정 테스트 실행기를 사용하고 클래스에 주석을 추가해야 함).

해결 방법은 매우 쉽습니다. 메서드의 가시성을 비공개에서 패키지 보호 (또는 보호)로 변경하기 만하면됩니다.

이를 구현하고 유지하는 데 시간을 투자해야합니다. 그리고 포인트 # 2와 그것이 이미 다른 도구 (powermock)에서 구현되었다는 사실은 의미가 없습니다.

마지막으로 ... 비공개 메소드를 조롱하는 것은 OO의 이해에 문제가 있다는 힌트입니다. OO에서는 메소드가 아닌 객체 (또는 역할)가 협업하기를 원합니다. 파스칼 및 절차 적 코드는 잊어 버리십시오. 사물에서 생각하십시오.


1
그 진술에 의해 만들어진 치명적 가정이 있습니다 :> 개인 메서드를 모킹하는 것은 OO의 이해에 문제가 있다는 힌트입니다. 공용 메서드를 테스트하고 있고 개인 메서드를 호출하는 경우 개인 메서드 반환을 모의하고 싶습니다. 위의 가정을 따르면 개인 메서드를 구현할 필요도 없습니다 . 어떻게 OO에 대한 이해가 부족한가요?
eggmatters

1
@eggmatters Baeldung에 따르면 "모킹 기술은 클래스 자체가 아니라 클래스의 외부 종속성에 적용되어야합니다. 클래스 테스트에 비공개 메서드의 모킹이 필수적이라면 일반적으로 잘못된 설계를 나타냅니다." 여기에 그것에 대한 멋진 스레드가 있습니다 softwareengineering.stackexchange.com/questions/100959/…
Jason Glez

34

다음은 powermock 으로 수행하는 방법에 대한 작은 예입니다.

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

method1 을 테스트하려면 코드를 사용하십시오.

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

개인 개체 obj 를 설정하려면 다음을 사용하십시오.

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);

귀하의 링크는 전원 모의를 가리키는은 @Mindaugas REPO
자비에

@Xavier 사실. 프로젝트에서 원하는 경우 사용할 수 있습니다.
Mindaugas Jaraminas

1
훌륭한 !!! 잘 이러한 간단한 예 목적은 단지 모든 프레임 워크 : 무엇을 제공하는 코드를 테스트하지 않는 것입니다 때문에 거의 모든 :) 설명
siddhusingh

이것을 업데이트하십시오. Whitebox는 더 이상 공용 API의 일부가 아닙니다.
user447607

17

어떤 방법이 있는지가 아니라 행동의 관점에서 생각해보십시오. 호출 된 메서드 는 true 인 method경우 특정 동작을 b합니다. b거짓 이면 다른 동작을합니다 . 이것은 두 개의 다른 테스트를 작성해야 함을 의미합니다 method. 각 경우에 하나씩. 따라서 세 가지 방법 지향 테스트 (하나는 method, 하나는 method1, 하나는 method2, 행동 지향 테스트는 두 개)가 있습니다.

이것과 관련이 있습니다 (최근에 다른 SO 스레드에서 이것을 제안했고 결과적으로 4 글자 단어로 불렸으므로 소금 한 알로 자유롭게 가져 가십시오.) 메서드 이름이 아닌 테스트중인 동작을 반영하는 테스트 이름을 선택하는 것이 도움이됩니다. 따라서 테스트 testMethod(), testMethod1()등을 부르지 마십시오 testMethod2(). 같은 이름과 같은 I calculatedPriceIsBasePricePlusTax()또는 taxIsExcludedWhenExcludeIsTrue()내가 테스트하고있는 무슨 행동을 나타내는 것을; 그런 다음 각 테스트 방법 내에서 표시된 동작 만 테스트합니다. 대부분의 이러한 동작에는 공용 메서드에 대한 한 번의 호출 만 포함되지만 개인 메서드에 대한 많은 호출이 포함될 수 있습니다.

도움이 되었기를 바랍니다.


13

Mockito는 이러한 기능을 제공하지 않지만 Mockito + JUnit ReflectionUtils 클래스 또는 Spring ReflectionTestUtils 클래스를 사용하여 동일한 결과를 얻을 수 있습니다 . 여기 에서 개인 메서드를 호출하는 방법을 설명하는 아래 예제를 참조하십시오 .

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

ReflectionTestUtils 및 Mockito를 사용한 전체 예제는 Mockito for Spring 책에서 찾을 수 있습니다.


ReflectionTestUtils.invokeMethod (student, "saveOrUpdate", "argument1", "argument2", "argument3"); invokeMethod의 마지막 인수는 전용 메서드에 전달해야하는 여러 인수를 사용할 수있는 Vargs를 사용합니다. 효과가있다.
Tim

이 답변은 더 많은 업 보트를 가져야하며, 개인 메서드를 테스트하는 가장 쉬운 방법입니다.
Max

9

개인 메서드를 테스트한다고 가정하지 않습니다. 어쨌든 비공개 메소드를 호출해야하므로 비공개가 아닌 메소드 만 테스트해야합니다. 비공개 메서드를 테스트하고 싶다면 디자인을 재고해야한다는 의미 일 수 있습니다.

적절한 종속성 주입을 사용하고 있습니까? 개인 메서드를 별도의 클래스로 이동하고 테스트해야 할 수도 있습니까? 이러한 방법은 비공개 여야합니까? ... 기본 또는 보호되지 않을 수 있습니까?

위의 경우 "무작위"라고하는 두 메서드는 실제로 자체 클래스에 배치하고 테스트 한 다음 위의 클래스에 주입해야 할 수 있습니다.


26
유효한 포인트. 그러나 메서드에 private 한정자를 사용하는 이유는 너무 길거나 반복적 인 코드를 잘라 내고 싶기 때문이 아닙니까? 다른 클래스로 분리하는 것은 긴 코드를 분할하고 반복되는 코드 라인을 방지하기위한 것이기 때문에 다른 곳에서는 재사용되지 않는 일류 시민이되도록 코드 라인을 홍보하는 것과 같습니다. 당신이 그것을 다른 클래스로 분리한다면 그것은 옳지 않다고 느끼게됩니다. 클래스 폭발을 쉽게 얻을 수 있습니다.
supertonsky 2013

2
슈퍼 톤 스키에 주목했는데 일반적인 경우를 언급하고있었습니다. 위의 경우 별도의 클래스에 속하지 않아야한다는 데 동의합니다. (- 당신이 개인 회원 홍보에 만들고있어 매우 유효한 지점입니다하지만 귀하의 코멘트에 +1)
자코 반 니케 르크

4
@supertonsky,이 문제에 대한 만족스러운 답변을 찾을 수 없었습니다. 내가 비공개 멤버를 사용하는 데에는 몇 가지 이유가 있으며 종종 그들은 코드 냄새를 나타내지 않으며 테스트하면 큰 도움이 될 것입니다. 사람들은 "그냥 하지마"라고 말함으로써 이것을 닦는 것 같습니다.
LuddyPants

3
죄송합니다. "비공개 메서드를 테스트하고 싶다면"디자인에 반대 투표를해야한다는 의미 일 수 있습니다. 좋습니다, 충분히 공평하지만 테스트를해야하는 이유 중 하나는 설계를 재고 할 시간이없는 기한 내에 설계를 변경해야하는 변경 사항을 안전하게 구현하려고하기 때문입니다. 개인 방법. 이상적인 세계에서 디자인이 완벽하기 때문에 개인 방법을 변경할 필요가 없습니까? 물론입니다.하지만 완벽한 세상에서는 테스트가 필요한 완벽한 세상에서는 모든 것이 작동합니다. :)
John Lockwood

2
@남자. 점수를 얻었습니다. 귀하의 반대표가 보장되었습니다 (+1). 의견을 보내 주셔서 감사합니다. 귀하의 의견에 동의합니다. 이러한 경우 두 가지 옵션 중 하나를 볼 수 있습니다. 메서드가 패키지 전용 또는 보호로 설정되고 단위 테스트가 평소대로 작성됩니다. 또는 (그리고 이것은 혀를 내밀고 나쁜 습관입니다) 주요 방법이 여전히 작동하는지 확인하기 위해 빠르게 작성됩니다. 그러나 내 응답은 원래 디자인을 변경할 수 없을 때 리팩토링하지 않고 새 코드가 작성되는 시나리오를 기반으로했습니다.
Jaco Van Niekerk

6

리플렉션을 사용하여 mockito를 사용하여 내부의 개인 메서드를 테스트 할 수있었습니다. 여기에 예가 있습니다.

//Service containing the mock method is injected with mockObjects

@InjectMocks
private ServiceContainingPrivateMethod serviceContainingPrivateMethod;

//Using reflection to change accessibility of the private method

Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
    Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
    //making private method accessible
    m.setAccessible(true); 
    assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));

6
  1. 리플렉션을 사용하면 테스트 클래스에서 개인 메서드를 호출 할 수 있습니다. 이 경우

    // 테스트 방법은 다음과 같습니다 ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        // invoke the private method for test
        privateMethod.invoke(A, null);
    
        }
    }
  2. private 메서드가 다른 private 메서드를 호출하는 경우 개체를 스파이하고 다른 메서드를 스텁해야합니다. 테스트 클래스는 다음과 같습니다.

    // 테스트 방법은 다음과 같습니다 ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }

**이 접근법은 반사와 물체 감시를 결합하는 것입니다. ** method1 및 ** method2는 private 메서드이고 method1은 method2를 호출합니다.


4

개인 방법을 테스트해야 할 필요성을 정말로 이해하지 못합니다. 근본적인 문제는 공용 메서드가 반환 유형으로 void를 가지고 있으므로 공용 메서드를 테스트 할 수 없다는 것입니다. 따라서 개인 방법을 테스트해야합니다. 내 추측이 맞습니까 ??

몇 가지 가능한 솔루션 (AFAIK) :

  1. 개인 메서드를 조롱하지만 여전히 메서드를 "실제로"테스트하지는 않습니다.

  2. 메서드에 사용 된 개체의 상태를 확인합니다. 대부분의 메서드는 입력 값의 일부 처리를 수행하고 출력을 반환하거나 개체의 상태를 변경합니다. 원하는 상태에 대한 개체 테스트도 사용할 수 있습니다.

    public class A{
    
    SomeClass classObj = null;
    
    public void publicMethod(){
       privateMethod();
    }
    
    private void privateMethod(){
         classObj = new SomeClass();
    }
    
    }

    [여기서 classObj의 상태가 null에서 not null로 변경되었는지 확인하여 private 메서드를 테스트 할 수 있습니다.]

  3. 코드를 약간 리팩터링하십시오 (레거시 코드가 아니기를 바랍니다). 메소드 작성의 기본은 항상 무언가 (int / 부울)를 반환해야한다는 것입니다. 반환 된 값은 구현에서 사용되거나 사용되지 않을 수 있지만 반드시 테스트에서 사용됩니다.

    암호.

    public class A
    { 
        public int method(boolean b)
        {
              int nReturn = 0;
              if (b == true)
                   nReturn = method1();
              else
                   nReturn = method2();
        }
    
        private int method1() {}
    
        private int method2() {}
    
    }

3

실제로 Mockito를 사용하여 비공개 멤버의 메서드를 테스트하는 방법이 있습니다. 다음과 같은 수업이 있다고 가정 해 보겠습니다.

public class A {
    private SomeOtherClass someOtherClass;
    A() {
        someOtherClass = new SomeOtherClass();
    }
    public void method(boolean b){
        if (b == true)
            someOtherClass.method1();
        else
            someOtherClass.method2();
    }

}

public class SomeOtherClass {
    public void method1() {}
    public void method2() {}
}

테스트하려면 a.method에서 메서드를 호출 SomeOtherClass하려면 다음과 같이 작성할 수 있습니다.

@Test
public void testPrivateMemberMethodCalled() {
    A a = new A();
    SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
    ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
    a.method( true );

    Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
}

ReflectionTestUtils.setField(); 당신이 감시 할 수있는 무언가로 비공개 회원을 스텁 할 것입니다.


2

테스트를 동일한 패키지에 넣고 다른 소스 폴더 (src / main / java 대 src / test / java)에 넣고 해당 메서드를 package-private로 만듭니다. Imo 테스트 가능성은 개인 정보 보호보다 중요합니다.


6
합법적 인 유일한 이유는 레거시 시스템의 일부를 테스트하는 것입니다. private / package-private 메서드를 테스트하기 시작하면 개체 내부를 노출합니다. 그렇게하면 일반적으로 리팩토링 가능한 코드가 좋지 않습니다. 객체 지향 시스템의 모든 장점으로 테스트 가능성을 얻을 수 있도록 구성을 참조하십시오.
Brice

1
동의합니다. 이것이 선호되는 방법입니다. 그러나 mockito로 비공개 메소드를 테스트하고 싶다면 이것이 유일한 (typesafe) 옵션입니다. 내 대답은 조금 성급했지만, 당신과 다른 사람들이 그랬던 것처럼 위험을 지적 했어야했습니다.
Roland Schneider 2012 년

이것이 제가 선호하는 방법입니다. 패키지 개인 수준에서 개체 내부를 노출하는 것은 잘못된 것이 아닙니다. 단위 테스트는 화이트 박스 테스트이므로 테스트를 위해 내부를 알아야합니다.
Andrew Feng

0

private 메서드가 void가 아니고 반환 값이 외부 종속성 메서드에 대한 매개 변수로 사용되는 경우 종속성을 모의하고를 사용 ArgumentCaptor하여 반환 값을 캡처 할 수 있습니다 . 예를 들면 :

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.