관리 메뉴

나만을 위한 블로그

[Android] 단위 테스트 시 static 메서드를 테스트하는 방법 본문

Android

[Android] 단위 테스트 시 static 메서드를 테스트하는 방법

참깨빵위에참깨빵 2022. 2. 6. 00:34
728x90
반응형

바로 이전 글인 단위 테스트에서 쉐어드 프리퍼런스를 사용하는 방법을 공부하다가 찾은 내용인데 글 제목과 다른 내용이라 별도의 포스팅으로 작성한다. 혹시 모르니 이전 글 링크를 아래에 첨부해둔다.

 

https://onlyfor-me-blog.tistory.com/444

 

[Android] 단위 테스트 시 쉐어드 프리퍼런스를 사용하는 방법

쉐어드 안에 저장된 값을 활용해서 API를 호출하는 부분을 단위 테스트로 테스트하려면 어떻게 해야 할지 궁금해서 찾아보다가 관련 라이브러리와 코드를 하나 찾게 됐다. 라이브러리 이름은 Moc

onlyfor-me-blog.tistory.com

 

메서드를 만들다 보면 static 메서드도 만들어 사용하는 경우가 있기 마련인데, 되겠지 하면서 static 메서드를 만들어 사용하면 에러가 발생한다. 어떤 에러가 발생하는지 직접 확인해보자.

 

import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public static String iAmCallPrintln() {
        return "나불렀어?";
    }

 

그리고 static 메서드를 테스트하는 테스트 메서드를 작성한다.

 

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

import org.junit.Test;

public class SharedTest {
    @Test
    public void testCallStaticMethod() {
        when(MainActivity.iAmCallPrintln()).thenReturn("나불렀어?");
        assertEquals("나불렀어?", MainActivity.iAmCallPrintln());
        System.out.println("문자열이 일치함");
    }
}

 

이제 테스트 파일을 실행하면 아래와 같이 에러가 발생한다.

 

when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
    1. you stub either of: final/private/equals()/hashCode() methods. Those methods *cannot* be stubbed/verified. Mocking methods declared on non-public parent classes is not supported.
    2. inside when() you don't call method on mock but on some other object.

org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
    1. you stub either of: final/private/equals()/hashCode() methods. Those methods *cannot* be stubbed/verified. Mocking methods declared on non-public parent classes is not supported.
    2. inside when() you don't call method on mock but on some other object.

 

다 보기엔 어질어질하고 귀찮으니 첫 문장만 구글 번역기에 넣고 돌리면 아래와 같이 번역된다.

 

when()에는 'mock에 대한 메서드 호출' 이어야 하는 인수가 필요하다

 

이게 무슨 일일까? static 메서드에 대한 차별이다

Mockito 라이브러리 자체가 static 메서드에 대한 테스트를 지원하지 않는 건가? 구글링해보니 몇 블로그의 포스팅이 눈에 띈다.

 

https://codechacha.com/ko/android-dexmaker-mockito/

 

Mockito - static, final method를 mocking하는 방법

Dexmaker의 Mockito 라이브러리를 이용하면 안드로이드에서 final, static method를 mocking, spying 할 수 있습니다. 또한 기존에 사용하던 Mockito API도 함께 사용할 수 있습니다. 안드로이드 프로젝트에서 Dexma

codechacha.com

Mockito는 final과 static 메서드를 mocking, spying하는 걸 지원하지 않는다

 

https://medium.com/androiddevelopers/mock-final-and-static-methods-on-android-devices-b383da1363ad

 

Mock final and static methods on Android devices

Android P+; use com.linkedin.dexmaker:dexmaker-mockito-inline-entended

medium.com

...(중략)...Mockito API는 아직 stub static 메서드에 대한 인터페이스를 제공하지 않으므로 새 인터페이스를 추가해야 한다. 일반 인스턴스 스터빙과의 가장 큰 차이점은 static 메서드의 스터빙을 재설정할 자연스런 위치가 없다는 것이다...(중략)

 

https://www.baeldung.com/mockito-mock-static-methods

테스트를 작성할 때 static 메서드를 mock해야 하는 상황을 종종 접하게 된다. Mockito 3.4.0 이전에는 static 메서드를 직접 mock할 수 없었다. 오직 PowerMockito가 있어야만 가능했다.

 

두 번째, 세 번째 링크에 들어가면 아래 코드와 다른 방식으로 static 메서드를 테스트 메서드에서 사용하니 관심있다면 참고하자.

결론은 현재 내가 갖고 있는 Mockito 의존성 문구만으로는 static 메서드를 테스트할 수 없다는 뜻이다.

그러면 어떻게 해야 할까? 다행히 다른 Mockito 의존성 문구가 존재하는데 이걸 쓰면 static 메서드를 테스트할 수 있다.

먼저 앱 수준 gradle에 의존성 문구를 복붙한다.

 

implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
testImplementation 'junit:junit:4.+'

androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
testImplementation "org.mockito:mockito-core:3.6.28"
testImplementation "org.mockito:mockito-inline:3.4.0" // <- 이걸 추가

 

이전 글을 보고 온 사람이라면 맨 마지막 문장만 추가하면 된다. 그리고 메인 액티비티에 대충 static 메서드를 만든다.

 

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    private SharedPreferences sharedPreferences;
    private SharedPreferences.Editor editor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sharedPreferences = getSharedPreferences("ddd", 0);
        editor = sharedPreferences.edit();
        editor.putString("key", "value");
        editor.putInt("number", 1);
        editor.apply();
    }

    public static String iAmCallPrintln() {
        return "나불렀어?";
    }

 

이제 테스트 코드를 작성한다.

 

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;

public class SharedTest {
    private static MockedStatic<MainActivity> aMainActivity;

    @BeforeClass
    public static void setBeforeClass() {
        aMainActivity = mockStatic(MainActivity.class);
    }

    @Test
    public void testCallStaticMethod() {
        when(MainActivity.iAmCallPrintln()).thenReturn("나불렀어?");
        assertEquals("나불렀어?", MainActivity.iAmCallPrintln());
        System.out.println("문자열이 일치함");
    }

    @AfterClass
    public static void setAfterClass() {
        aMainActivity.close();
    }
}

 

MockedStatic, mockStatic()이란 뉴페이스가 있다. 각각이 무엇인지 간단하게 확인해보자.

 

https://javadoc.io/static/org.mockito/mockito-core/4.3.1/org/mockito/MockedStatic.html

 

MockedStatic (Mockito 4.3.1 API)

Represents an active mock of a type's static methods. The mocking only affects the thread on which this static mock was created and it is not safe to use this object from another thread. The static mock is released when this object's ScopedMock.close() met

javadoc.io

형식(type)의 static 메서드에 대한 active mock을 나타낸다. mocking은 이 static mock이 생성된 쓰레드에만 영향을 미치며 다른 쓰레드에서 이 객체를 사용하는 건 안전하지 않다. 이 객체의 ScopedMock.close()가 호출되면 static mock이 해제된다. 이 객체가 닫히지 않으면 static mock 객체는 시작 쓰레드에서 활성 상태로 유지된다. 따라서 예를 들어 JUnit 규칙이나 확장을 사용해 명시적으로 관리되는 경우가 아니면 try-with-resources 문 안에서 이 객체를 만드는 것이 좋다. @Mock 어노테이션이 이 유형(type)의 필드 또는 메서드 매개변수에 쓰이는 경우 일반 mock 대신 static mock이 생성된다. static mock은 관련 테스트를 완료하면 활성화되고 해제된다.

 

대개 try-with-resource 문에서 사용되고 close()를 써서 생성한 static mock을 해제하는 처리도 필요한 듯하다.

그래서 위에서 @AfterClass 어노테이션이 붙은 메서드 안에서 close()를 호출해 static mock을 해제하는 걸 볼 수 있다.

mockStatic()에 대한 설명은 아래와 같다.

 

https://www.baeldung.com/mockito-mock-static-methods

Mockito 3.4.0부터 Mockito.mockStatic(Class<T> classToMock)를 써서 static 메서드 호출에 대한 호출을 mock할 수 있다. 이 메서드는 범위가 지정된 mock 객체에 대한 MockedStatic 객체를 리턴한다. 범위가 지정된 mock은 mock을 활성화하는 엔티티가 닫아야 한다는 점이 중요하다...(중략)

 

mockStatic()의 매개변수로 static 메서드가 들어있는 클래스를 넣어 테스트 파일에서 static 메서드를 호출할 수 있게 해주는 것 같다.

위의 코드로 보면 MockedStatic 클래스의 제네릭 안에 메인 액티비티를 넣어서 만든 aMainActivity 객체를 메인 액티비티와 매핑시켜서 mock 객체를 만들어주는 메서드라고 이해했다.

당연한 거지만 @BeforeClass, @AfterClass가 붙은 메서드들은 모두 static인 것에 주의하자.

그런데 메인 액티비티 안의 static 메서드를 호출할 때는 aMainActivity가 아니라 MainActivity를 사용한다. 왜 그럴까? 직접 aMainActivity를 넣어보면 알 수 있다.

 

저 테스트 파일을 실행하면 녹색 체크 표시가 나오며 테스트 완료가 표시되고, 결과창을 조금 내려보면 println()이 호출된 걸 볼 수 있다.

 

 

만약 일부러 틀린 값을 넣고 테스트를 실패시켜보면 이런 화면이 나온다.

 

 

 

참고한 사이트)

 

https://www.crocus.co.kr/1705

 

Mockito를 이용하여 static method 유닛 테스트

아래와 같이 간단한 클래스가 있다고 가정해보자. package com.example.mockedstaticunittest; public class Calculator { public static int add(int x, int y) { return x + y; } public int sub(int x, int y)..

www.crocus.co.kr

 

반응형
Comments