관리 메뉴

나만을 위한 블로그

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

Android

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

참깨빵위에참깨빵 2022. 2. 5. 23:53
728x90
반응형

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

라이브러리 이름은 Mockito라는 것이다. 이 단어는 모히또로 발음하나 생각해서 사전에 찾아보니 모히또 칵테일을 닮은 무알콜 음료라고 나온다.

아무튼 이 Mockito 라이브러리의 의존성 문구를 앱 수준 gradle에 복붙해준다. 프로젝트 수준 gradle 파일에도 넣어야 하는 게 있긴 한데 안드로이드 프로젝트 빌드 시 기본으로 설정돼 있기 때문에 굳이 안 넣어도 된다.

아래는 내가 라이브러리 테스트할 때 사용한 앱 수준 gradle이다. 오늘 날짜로 3.6.28 버전이 가장 최신 버전이다.

 

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"

 

그 다음 메인 액티비티에 대충 쉐어드와 에디터를 선언하고 String과 int 값을 저장했다.

 

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();
    }

 

JUnit을 사용해 순수 자바 로직에 대한 단위 테스트를 할 것이기 때문에 XML은 없어도 된다.

이제 (test)가 써진 패키지에 자바 클래스를 만들고 본격적으로 테스트 코드를 작성한다.

 

import static org.junit.Assert.assertEquals;

import android.content.Context;
import android.content.SharedPreferences;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

public class SharedTest {
    private SharedPreferences sharedPrefs = Mockito.mock(SharedPreferences.class);

    @Before
    public void before() {
        this.sharedPrefs = Mockito.mock(SharedPreferences.class);
        Context context = Mockito.mock(Context.class);
        Mockito.when(context.getSharedPreferences("ddd", 0))
                .thenReturn(sharedPrefs);
    }

    @Test
    public void testGetStringValueFromShared() {
        Mockito.when(sharedPrefs.getString("key", ""))
                .thenReturn("value");
        assertEquals("value", sharedPrefs.getString("key", ""));
        System.out.println("키에 저장된 값이 value와 일치함");
    }
}

 

 

Context는 before() 안에서만 사용되기 때문에 굳이 전역으로 빼지 않아도 될 것 같다. 만약 Context가 필요한 다른 작업이 있다면 그 땐 전역으로 빼서 사용하자. 이 테스트 파일을 실행하면 왼쪽에 녹색 체크 표시가 나오며 테스트가 성공하는데, 결과창의 스크롤을 조금 밑으로 내리면 println()으로 출력되는 문자열이 보일 것이다.

thenReturn()에 잘못된 값을 입력했다면 아래처럼 빨간 체크 표시가 나오면서 테스트가 실패하고 실패 원인을 알려준다.

당연히 assertEquals() 안의 1번 매개변수 값을 바꿔도 아래와 똑같은 문구를 볼 수 있다.

 

저 파란색으로 링크된 텍스트뷰를 클릭하면 아래 그림이 나온다. 그냥 위에 나온 결과를 좀 더 큰 팝업창에 보여줄 뿐인 기능이다. 이 창이 필요하게 될지는 잘 모르겠다.

 

@Before, @Test 어노테이션이 뭔지 잘 모르겠다면 아래 링크를 참고해보자.

 

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

 

[Android] 유닛 테스트란? 유닛 테스트 예시(JAVA)

※ 이 글에서 사용한 안드로이드 스튜디오 버전은 4.2.2다. 위키백과에서 말하는 유닛(단위) 테스트란 아래와 같다. https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%9B_%ED%85%8C%EC%8A%A4%ED%8A%B8 유닛 테스트..

onlyfor-me-blog.tistory.com

 

Mockito 클래스에서 호출한 when(), thenReturn() 메서드의 역할은 아래와 같다.

 

  • mock() : 주어진 클래스 or 인터페이스의 모의 객체를 만들 때 사용한다. 이 메서드의 매개변수로 아무것도 넘기지 않으면 기본값이 리턴된다고 하는데, mock() 안에 아무것도 안 넣어보니 빨간 줄이 생기면서 컴파일 에러가 발생한다. 이 메서드의 매개변수로 넣어줄 수 있는 유형은 아래 4가지다. 각 mock()의 원형도 오른쪽에 첨부했다.
    • 클래스와 인터페이스 - <T> mock(Class<T> classToMock)
    • Answer - <T> mock(Class<T> classToMock, Answer defaultAnswer)
    • MockSettings - <T> mock(Class<T> classToMock, MockSettings mockSettings)
    • String - <T> mock(Class<T> classToMock, String name)
  • when() : 특정 메서드가 호출되면 특정 값을 반환하게 할 때 사용한다. 위 코드에선 context를 통한 getSharedPreferences()의 결과값을 리턴하므로 결국 값을 빼거나 넣을 때 사용할 수 있는 쉐어드 프리퍼런스를 얻게 된다.
  • thenReturn() : when()과 주로 같이 사용되는 메서드. Mock(모의) 객체의 특정 메서드가 호출될 때 리턴값을 정의한다. 위 코드에선 sharedPrefs라 명명한 쉐어드 프리퍼런스가 리턴될 것이기 때문에 sharedPrefs를 넣은 것이다.

 

mock의 사전적 정의는 "거짓된 가짜의, 모의의" 라는 뜻이 있기 때문에 mock()은 테스트에 사용할 모의 클래스를 만들어내는 메서드라고 이해했고, when()과 thenReturn()은 "이 메서드를 호출했을 때, 그러면 이 값을 리턴해야 한다" 라는 흐름이 있다고 생각해서 이해하려고 한다.

mock()에 넣을 수 있는 유형으로 ReturnValues도 있는데 Answer가 나오면서 더 이상 사용되지 않는다.

 

마지막으로 위에서 적은 Answer와 MockSettings가 뭔지 짧게 확인하고 포스팅을 마무리한다.

Mockito의 자바독에선 Answer를 아래와 같이 설명한다.

 

https://javadoc.io/static/org.mockito/mockito-core/3.2.4/org/mockito/stubbing/Answer.html

 

Answer (Mockito 3.2.4 API)

Generic interface to be used for configuring mock's answer. Answer specifies an action that is executed and a return value that is returned when you interact with the mock. Example of stubbing a mock with custom answer: when(mock.someMethod(anyString())).t

javadoc.io

모의 답변을 구성하는 데 사용할 일반 인터페이스다. Answer는 실행되는 작업과 모의 객체와 상호작용 시 리턴되는 리턴값을 지정한다. 아래는 사용자 정의 답변으로 모의 스텁을 만드는 예시다
when(mock.someMethod(anyString())).thenAnswer(
     new Answer() {
         public Object answer(InvocationOnMock invocation) {
             Object[] args = invocation.getArguments();
             Object mock = invocation.getMock();
             return "called with arguments: " + Arrays.toString(args);
         }
 });

 //Following prints "called with arguments: [foo]"
 System.out.println(mock.someMethod("foo"));

 

여기서 스텁이란 미리 프로그래밍된 리턴값과 함께 제공되는 가짜 클래스를 말한다. 테스트 중인 클래스에 주입되어, 입력으로 테스트 중인 항목을 제어할 수 있다. 켄트 백의 '테스트 주도 개발' 책의 역자는 스텁에 대해 아래와 같이 설명한다.

스텁 구현(stub implementation)은 메서드의 서명부와 (반환값이 있을 경우) 반환 명령만 적는 식으로 해서 이 메서드를 호출하는 코드(이 경우 테스트 코드)가 컴파일 될 수 있도록 껍데기만 만들어두는 것을 뜻한다

 

메서드의 서명부는 시그니처를 말한다. 위 코드로 치면 public Object answer(InvocationOnMock invocation) 부분을 시그니처라고 말한다.

다음은 같은 자바독에서 설명하는 MockSettings 인터페이스다.

 

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/MockSettings.html

 

MockSettings - mockito-core 4.3.1 javadoc

Latest version of org.mockito:mockito-core https://javadoc.io/doc/org.mockito/mockito-core Current version 4.3.1 https://javadoc.io/doc/org.mockito/mockito-core/4.3.1 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org.m

javadoc.io

추가 Mock 설정으로 Mock 생성을 허용한다. 너무 자주 사용하지 마라. 간단한 Mock을 사용하는 간단한 테스트를 작성하는 걸 고려하라...(중략)...간단한 방법으로 테스트를 작성할 수 없다면 테스트 중인 코드를 리팩토링하라
//Creates mock with different default answer and name
Foo mock = mock(Foo.class, withSettings()
                            .defaultAnswer(RETURNS_SMART_NULLS)
                            .name("cool mockie"));

//Creates mock with different default answer, descriptive name and extra interfaces
Foo mock = mock(Foo.class, withSettings()
                            .defaultAnswer(RETURNS_SMART_NULLS)
                            .name("cool mockie")
                            .extraInterfaces(Bar.class));

 

내가 원하는 모의 객체를 만들 수 있게 해주는 인터페이스 같은데 자주 쓰면 뭔가 문제를 일으키는 놈 같다. 현재로서는 Answer, MockSettings 인터페이스를 사용해 단위 테스트할 일은 없으니 여기까지만 확인하고 넘긴다.

반응형
Comments