source

모키토 슈퍼클래스의 메서드 호출만을 조롱하는 방법

nicesource 2022. 12. 24. 17:21
반응형

모키토 슈퍼클래스의 메서드 호출만을 조롱하는 방법

몇몇 테스트에서 모키토를 사용하고 있어요.

다음과 같은 수업이 있습니다.

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

요.super.save의 개요ChildService첫 번째 콜은 실제 메서드를 호출해야 합니다.그게게 할수 는? ?? ???

리팩터링을 선택할 수 없는 경우 슈퍼메서드 콜의 모든 것을 조롱/정지할 수 있습니다.

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }

아니요, 모키토는 지원하지 않습니다.

이것은 여러분이 찾고 있는 답이 아닐 수도 있지만, 여러분이 보고 있는 것은 설계 원칙을 적용하지 않는 증상입니다.

상속보다 구성을 선호하다

슈퍼클래스를 확장하는 대신 전략을 추출하면 문제가 사라집니다.

그러나 코드를 변경할 수 없지만, 어쨌든 테스트해야 하고, 이렇게 어색한 방법으로도 희망은 남아 있습니다.일부 AOP 도구(예: AspectJ)를 사용하면 슈퍼 클래스 메서드에 코드를 짜넣어 실행(우크)을 완전히 피할 수 있습니다.프록시를 사용하는 경우 이 방법은 작동하지 않습니다. 바이트 코드 수정(로드 시간 위빙 또는 컴파일 시간 위빙)을 사용해야 합니다.PowerMock이나 PowerMockito와 같은 이러한 트릭을 지원하는 조롱 프레임워크도 있습니다.

리팩터링에 가보는 게 좋을 것 같은데, 그게 선택사항이 아니라면 심각한 해킹 재미에 빠져들게 될 거야.

ChildService.save() 메서드에서 다른 메서드로 코드를 리팩터링하여 ChildService.save()를 테스트하는 대신 새로운 메서드를 테스트해 보십시오.이렇게 하면 슈퍼 메서드에 대한 불필요한 호출을 피할 수 있습니다.

예:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 

Power Mockito를 사용하여 슈퍼클래스 방식을 억제하는 방법을 찾았습니다.이를 위해 필요한 3가지 간단한 절차

  1. Power Mockito를 사용합니다.suppress method 및 MemberMatcher.methodsDeclaredIn 메서드를 사용하여 부모 클래스의 메서드를 억제합니다.

  2. @PrepareForTest에서 번째 부모 클래스 추가

  3. PowerMock을 사용하여 테스트 클래스를 실행합니다.즉, 테스트 클래스 위에 @RunWith(PowerMockRunner.class)를 추가합니다.

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({BaseService.class})
    public class TestChildService(){
    
        @Spy
        private ChildService testChildServiceObj = Mockito.spy(new ChildService());
    
        @Test
        public void testSave(){
            PowerMockito.suppress(MemberMatcher.methodsDeclaredIn(BaseService.class));
    
            //your further test code
    
            testChildServiceObj.save();
        }
    }
    

주의: 이것은 슈퍼클래스 메서드가 아무것도 반환하지 않는 경우에만 작동합니다.

상속이 타당하다면 새로운 방법(프라이빗 패키지)을 만드는 것이 가장 쉬운 방법일 수 있습니다.super(superFindall이라고 부릅니다)를 호출하고 실제 인스턴스를 감시한 후 부모 클래스를 조롱하는 방법으로 superFindAll() 메서드를 조롱합니다.커버리지와 가시성 면에서 완벽한 솔루션은 아니지만, 기능을 발휘하고 적용하기 쉽습니다.

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}

super class 메서드를 호출하는 subclass에 package protected(같은 패키지에 포함된 테스트클래스) 메서드를 생성하여 오버라이드된 subclass 메서드로 해당 메서드를 호출합니다.그런 다음 스파이 패턴을 사용하여 테스트에서 이 방법에 대한 기대를 설정할 수 있습니다.예쁘지는 않지만 확실히 당신의 테스트에서 슈퍼 방법에 대한 모든 기대 설정을 처리해야 하는 것보다 낫다.

Iwein 응답에 전적으로 동의하더라도(

상속보다 작문을 선호하다

상속이 당연한 것처럼 느껴질 때가 있다는 것을 인정합니다.또, 유닛 테스트의 이유만으로 그것을 망가뜨리거나 리팩터링 하거나 하지 않습니다.

그래서, 제 제안은:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

그리고 단위 테스트에서 다음을 수행합니다.

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done

PowerMockito를 사용하여 이를 수행하고 부모 클래스 메서드의 동작만 자식 클래스 메서드의 테스트를 계속하는 것으로 대체할 수 있습니다.예를 들어 문자열과 같은 메서드가 값을 반환하고 있는 경우에도 다음과 같은 작업을 수행할 수 있습니다.

@RunWith(PowerMockRunner.class)
@PrepareForTest({ BaseService.class })
public class TestChildService() {

    private BasicService basicServiceObj;
    private ChildService testee;

    @Before
    public void init() {
        testee = new ChildService();
        basicServiceObj = PowerMockito.spy(new BaseService());
        PowerMockito.doReturn("Result").when(basicServiceObj, "save", ... optionalArgs);
    }

    @Test
    public void testSave(){
        testee.save();
    }
}

아무것도 반환하지 않는 경우(void그럼 대신doReturn사용할 수 있습니다.doNothing. 일부 추가optionalArgs메서드에 몇 가지 인수가 있을 경우 해당 부분을 건너뜁니다.

그 이유는 베이스 클래스가 퍼블릭에드가 아니기 때문에 Mockito는 가시성을 이유로 인터셉트할 수 없고, 베이스 클래스를 퍼블릭으로 변경하거나 서브 클래스의 @Override(퍼블릭으로)를 변경하면 Mockito가 올바르게 조롱할 수 있기 때문입니다.

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}

대부분의 경우에 효과가 있는 간단한 접근법이 있습니다.오브젝트를 감시하여 조롱할 메서드를 스텁할 수 있습니다.

다음은 예를 제시하겠습니다.

MyClass myObjectSpy = Mockito.spy(myObject);
org.mockito.Mockito.doReturn("yourReturnValue").when(mySpyObject).methodToMock(any()..);

따라서 객체를 테스트할 때 myObjectSpy를 사용할 수 있으며 methodToMock이 호출되면 mock 메서드에 의해 일반 동작을 덮어씁니다.

반환이 있는 메서드의 코드입니다.void 메서드가 있는 경우 대신 doNothing을 사용할 수 있습니다.

언급URL : https://stackoverflow.com/questions/3467801/mockito-how-to-mock-only-the-call-of-a-method-of-the-superclass

반응형