Android Activity Testing 


 테스팅은 잘 작동하는 어플리케이션을 만들기 위해 피할 수 없는 작업입니다. 일반적으로 회사에서 어플리케이션을 개발하는 와중에는 별도의 테스팅 팀이 관련된 일을 수행하고 우울한 리포트를 보내오곤 하지만, 개인 개발자 자격으로 어플을 개발할 때는 아무래도 귀찮은 나머지 빼먹기 일 수인 과정이지요. 안드로이드에서는 편리하고 강력한 테스팅 툴을 제공해주고 있습니다만, 개인적으로는 제대로 사용해 본 적은 없었습니다. 그런데 얼마전 부터, 안드로이드 테스팅 환경에 관한 글이 안드로이드 개발자 사이트 여기저기에 올라오기 시작했습니다. 큰 마음먹고, 스스로 공부하는 기분으로 정리하는 기분으로 하나 하나 차근 차근 번역해 보았습니다.생각보다 별로 재미 없더군요-_-;; 그런 와중에 그나마 가장 유용했던 Activity Testing 관련된 튜터리얼 번역문서를 안드로이드펍 분들과 공유하고자 합니다. 길고 긴 문서입니다만, 테스트에 관심있으신 분들은 느긋하게 한번 읽어보세요.

 안드로이드는 SDK 와 통합된 쉽고도 강력한 테스트 프레임워크를 제공 한다. JUnit 을 확장하여, 안드로이드 시스템 객체를 임시로 대체할 수 있는 Mock 클래스와 테스트 중에 어플리케이션을 컨트롤 할 수 있는 Instrumentation 기능이 추가적으로 지원된다. 안드로이드 테스팅 환경 전반에 관한 내용은 Testing Android Application 문서 를 참고하라. 

 이 튜터리얼은 간단한 안드로이드 어플리케이션을 시작으로 차근 차근 테스트 어플리케이션을 만드는 방법을 설명한다. 설명될 테스트 어플리케이션은 다음과 같은 특징을 갖는다.

  • 안드로이드 테스트는 그것 자체로 하나의 안드로이드 어플리케이션이며 메니페스트 파일을 통해 테스트 대상 어플리케이션과 연결된다. 

  • 안드로이드 어플리케이션 구성 요소 대신, 하나 이상의 테스트 케이스로 구성되며, 각각의 테스트 케이스는 서로 다른 클래스로 정의된다. 

  • 안드로이드 테스트 케이스 클래스는 JUnit 의 TestCase 클래스를 상속받아 만들어진다.

  • Activity 를 테스트 하기 위한 테스트 케이스 클래스는 Instrumentation 기능을 이용하여, 테스트 대상 어플리케이션과 연결된다. 이 기능을 통해 여러분은 대상 어플리케이션 UI 에 키 나 터치 이벤트를 직접 전달할 수 있다. 

  • 여러분은 테스팅 할 어플리케이션 구성요소 종류에 맞추어 테스트 케이스 클래스를 선택해야 한다. 

  • Eclipse/ADT 는 테스트 어플리케이션을 생성하고, 실행하고, 결과를 확인할 수 있는 추가적인 도구를 제공한다.

 데모에 사용되는 테스트 어플리케이션은 아래와 같은 테스트를 수행하기 위한 메서드를 포함한다.

  • 초기 조건 테스트 - 어플리케이션에 올바르게 초기화 되었는지 테스트 한다. 이 것은 어플리케이션의 onCreate() 메서드에 대한 단위 테스트라고 할 수 있다. 또한, 이 테스트는 이후 이루어지는 테스트들이 올바르게 작동하고 있는지 확인하는데 유용하다.

  • UI 테스트 - 메인 UI 기능이 정상적으로 작동하는지 테스트 한다. 이 예제는 Activity 를 테스트 하는데 Instrumentation 을 사용하는 방법을 설명한다. 여러분이 자동화된 UI 테스트를 수행하기 위해, 메인 어플리케이션으로 키 이벤트를 전달하는 방법을 설명한다. 

  • 상태 관리 테스트 - 어플리케이션이 상태 정보를 잘 저장하는지에 관해 테스트 한다. 이 예제는 다양한 어플리케이션 구성요소를 테스트하는데 활용 가능한, Instrumentation Test Runner 의 기능에 관해 설명한다. 

Prerequisites

이 문서와 튜터리얼에 사용된 코드는 아래와 같은 전제 하에 작성되었다.

  • 안드로이드 프로그래밍에 관한 기본 지식. 만일 여러분이 안드로이드 어플리케이션을 작성해 본 경험이 없다면, Hello, World 튜터리얼을 한번 해보길 권장 한다. 테스트에 사용될 Spinner 에 관해 좀 더 알고 싶다면, Hello Views > Spinner 예제를 참고하라.

  • 안드로이드 테스트 프레임워크에 관한 기본 지식. 이에 관한 기본적인 내용은Testing Android Applications 와 Hello, Testing 문서를 참고하라. 

  • ADT 가 설치된 Eclipse. 이 튜터리얼은 Eclipse ADT 를 이용하여 어떻게 테스트 프로젝트를 설정하고 실행하는지 설명한다. 

  • 안드로이드 SDK 1.5(API Level 3) 이상. 이 예제는 안드로이드 1.5 에서 처음 선보인 API 들을 사용한다. 

Installing the Tutorial Sample Code

 이 튜터리얼은 SDK 에 포함된 샘플 코드를 기반으로 작성되었다. 구체적으로, SDK 에 포함된 두 개의 예제 프로그램을 사용한다. 

  • Spinner 는 테스트 대상이 되는 어플리케이션이다. 이 튜터리얼은 테스트 어플리케이션을 작성하는 방법에 관해 초점을 맞추고 있기 때문에, 테스트 대상 어플리케이션은 기존에 제공되는 Spinner 어플리케이션을 사용한다. 

  • SpinnerTest 는 테스트 어플리케이션이다. 튜터리얼에서 이 테스트 어플리케이션을 작성하는 방법에 관해 단계별로 설명할 것이다. 만일 여러분이 재빠르게 튜터리얼을 살펴 보고 싶다면, 온전한 SpinnerTest 어플리케이션을 미리 설치해 둘 수도 있다. 그 방법은 글 말미의 부록에서 다루고 있다.

예제 어플레킹션은 'Samples for SDK API 8' 이라는 이름으로 SDK에 포함되어 있다. 튜터리얼을 시작하기에 앞서, 안드로이드 SDK 과 AVD 매니저를 이용하여 올바른 버전의 샘플을 설치해보자.

  1. Eclipse 에서, Window > Android SDK and AVD Manager 를 선택하라.

  2. Installed Packages 패널을 열과, "Samples for SDK API 8" (혹은 그 이상) 이 있는지 확인하라. 만일 그렇다면, Setting Up th Project 섹션으로 바로 넘어가라. 여러분은 튜터리얼을 시작할 준비가 되어있다.

  3. Available Packages 패널을 열어라.

  4. "Samples for SDK API 8" 컴포넌트를 선택하고, Install Selected 를 클릭하라.

  5. 컴포넌트를 확인하고 Install Accepted 를 클릭하라. 샘플 컴포넌트가 SDK 에 설치될 것이다. 

샘플 어플리케이션은 <sdk>/samples/android-8/  에 저장된다. 샘플에 관한 보다 일반적인 내용은 Getting the Samples 을 참고하라.

Note: 예제에 사용된 튜터리얼이 "Samples for SDK API 8" 이라는 이름을 갖고 있지만, 반드시 안드로이드 2.2 이상에서 어플리케이션을 빌드하고 실행할 필요는 없다. 이 이름은 단지 샘플 어플리케이션이 처음 빌드된 플랫폼 브랜치를 가리키고 있을 뿐이다.

Setting Up the Emulator

 이 튜터리얼에서는 안드로이드 에뮬레이터를 사용한다. 에뮬레이터는 여러분이 이전 단계에서 설정한 API Level 보다 상위의 AVD 를 필요로 한다. AVD 버전을 확인하고, 새롭게 생성하는 방법은 Creating an AVD 문서를 참고하라.

 AVD 와 에뮬레이터를 테스트하는 셈 치고, SpinnerActivity 어플리케이션을 한번 실행 시켜 보라. 어플리케이션이 실행되면, 아래 방향을 가리키는 큰 화살표를 클릭해 보자. Spinner 가 확장되고, 'Select a planet' 이라는 제목이 보일 것이다. 여러 행성 목록 중에 하나를 선택하면, Spinner 가 닫히고, 여러분이 선택한 내용이 화면에 출력될 것이다. 

Setting Up the Projects

본격적인 테스트에 앞서 우선 Spinner 와 SpiinerTest 프로젝트를 설정해야한다. Spinner 어플리케이션은 아무런 변경없이 그냥 사용하면 됨으로, 기존 소스를 기반으로 안드로이드 프로젝트를 생성하면 된다. Spiiner 어플리케이션을 테스트할 테스트 어플리케이션은 새로운 어플리케이션이며, 여러분은 이 튜터리얼에서 설명되는 코드를 추가하여 어플리케이션을 완성하게 될 것이다.

Spiiner 어플을 새로운 안드로이드 프로젝트로 추가하기 위한 방법은 다음과 같다.

  1. Eclipse 에서 File > New > Project > Android > Android Project 을 선택한 후, Next 를 클릭한다. New Android Project 다이얼로그가 나타난다.

  2. Project name 텍스트 박스에,"SpinnerActivity 라고 입력한다.

  3. Contents 영역 중에, "Create project from existing source" 항목을 체크하라.

  4. Location 값을 설정하기 위하여, Browse 버튼을 클릭한 후, <SDK_path>/samples/android-8/Spinner 경로를 선택히 Open 을 클릭하라. 

  5. Build Target 영역에서, API level 을 3 이상으로 설정하라. 

  6. Properties 영역의 Min SDK Version: 값을 "3" 으로 입력하라.

아래의 스크린샷은 설정된 값을 요약해서 보여준다.



새로운 SpinnerTest 프로젝트는 다음과 같은 순서로 생성된다. 

  1. Next 를 클릭하면, New Android Test Project 다이얼로그가 나타나낟.
  2. "Create a Test Project" 항목을  체크하라.
다른 값들은 수정할 필요가 없다. 결과는 다음과 같을 것이다.


 이제 여러분은 테스트할 어플리케이션과 빈 테스트 어플리케이션 프로젝트를 생성했다. 두 개의 프로젝트를 서로 다른 디렉토리에 위치하지만, Eclipse ADT 가 알아서 두 개의 프로젝트를 연결해 줌으로 여러분은 걱정 할 필요가 없다. 

 ADT 는 테스트 어플리케이션을 위해 필요한 몇 가지 초기 설정을 자동으로 수행한다. SpinnerActivityTest 프로젝트를 선택해보면 이미 메니페스트 파일이 생성되어 있음을 볼 수 있다. 테스트 프로젝트를 추가하면, Eclipse 는 자동으로 메니페스트 파일을 생성하고, Instrumentation 기능을 사용할 수 있도록 설정해준다. 메니페스트 파일을 살펴보면, 다음과 같은 내용을 확인할 수 있다. 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.android.example.spinner.test"
      android:versionCode="1"
      android:versionName="1.0">
   
<application android:icon="@drawable/icon" android:label="@string/app_name">     <uses-library android:name="android.test.runner" />     </application>
   
<uses-sdk android:minSdkVersion="3" />
   
<instrumentation
       
android:targetPackage="com.android.example.spinner"
       
android:name="android.test.InstrumentationTestRunner" />
</manifest>

 <instrumentation> 엘리먼트가 추가되어 있으며, 속성 값에 android:targetPackage = "com.android.example.spinner" 이라고 테스트 대상 어플리케이션 패키지 이름이 명시되어 있다. 아늗로이드는 이 내용을 기반으로, 테스트 수행시 어떤 어플리케이션을 실행할 지 알 수 있다. android.test.InstrumentationTestRunner 속성 값은 이 테스트 어플리케이션이 Instrumentation 기능을 지원하는 Test Runner 를 이용하여 실행되야함을 나타낸다. 

Creating the Test Case Class

 이제 여러분은 SpinnerActivityTest 프로젝트를 생성했다. SpinnerActivityTest 프로젝트에는 수행될 테스트를 담을 테스트 클래스만을 제외하고, 어플리케이션을 빌드하고 실행하는데 필요한 모든 요소들이 포함되어 있다.

 다음 단계는, 테스트 케이스 클래스를 정의하는 것이다. 여러분은 다음과 같은 요소를 포함하는 테스트 케이스 클래스를 만들게 될 것이다.

  • Test setup - JUnit setUp() 메서드를 이용하여, 테스트 수행전에 해야할 일들을 처리한다. 
  • 선결 조건 테스트 - 좋은 테스팅 기법이다. 주요 Activity 테스트에 앞서, 테스트 대상 어플리케이션 구성 요소들이 정상적으로 초기화되었는지등을 테스트 한다. 이 테스트가 실패한다면, 여러분은 이후에 진행될 다른 모든 테스트 결과가 의미 없다는 것을 알 수 있다. Note: 선결 조건 테스트와 setUp() 메서드는 그 용도가 다르다. setUp() 메서드는 각 테스트 메서드가 호출되기 전에 매번 호출되며, 테스트 환경을 깔끔하게 정리하기 위해 사용된다. 선결 조건 테스트는 한 번만 실행되며, 테스트 대상 어플리케이션이 정상적으로 실행되었는가를 확인하는데 사용된다.
  • UI 테스트 - 이 테스트는 Instrumentation 기능을 이용하여 어떻게 메인 어플리케이션의 UI 를 컨트롤 할 수 있는지 보여준다. 
  • 상태 정보 테스트 - 이 테스트는 어플리케이션이 안드로이드 시스템 상에서 얼마나 잘 상태를 유지하는지 테스트 할 수 있는 몇 가지 기법들을 보여준다. 만족스러운 사용자 경험을 제공하기 위해서는 여러분이 작성한 어플리케이션은 결코 현재 상태 정보를 까먹어서는 않된다. 안드로이드Activity 생명주기는 전화가 걸려오거나 메모리가 부족한 상황에서 어플리케이션이 갑작스럽게 종료되는 경우에도 상태 정보를 관리할 수 있는 방법들을 제공해주며, SpinnerActivity  는 그런 기능들을 활용하도록 구현되어 있다. 이 테스트는 SpinnerActivity 상에서 구현한 내용이 잘 작동하는지 확인할 것이다.

 안드로이드 테스트 메서드는 테스트 클래스 내에 포함된다. 여러분은 우선 테스트할 대상 어플리케이션 구성 요소에 맞추어, 상속받을 테스트 클래스를 선택해야한다. 이 튜터리얼에서, 테스트 대상 어플리케이션은 하나의 Activity 로 이루어져 있음으로, ActivityInstrumentationTestCase2 를 사용한다. 이 클래스는 대상 어플리케이션의 UI 와 직접적으로 상호작용할 수 있는 편리한 메서드들을 제공한다. 

Adding the test case class file

 ActivityInstrumentationTestCase2 를 기반으로한 테스트 케이스 클래스를 추가해 보자.

1. 패키지 익스플로러에서, SpinnerActivityTest 를 선택한다.

2. android.example.spinner.test. 패키지를 선택하고, 마우스 우클릭한 후, New > Class 를 선택한다.


3. New Java Class 마법사가 나타난다.



4. 다음과 같이 입력하라.
Name: "SpinnerActivityTest". This becomes the name of your test class.
Superclass: "android.test.ActivityInstrumentationTestCase2<SpinnerActivity>".
5. 다른 값은 변경하지 말고, 'Finish' 를 클릭한다.
6. Import 문제를 해결하기 위해 다음의 구문을 추가하라.
import com.android.example.spinner.SpinnerActivity;

Adding the test case constructor

 테스트 어플리케이션을 올바르게 인스턴스화하기 위해, 여러분은 테스트 케이스 클래스의 생성자를 추가해 주어야 한다. Test Runner 가 해당 테스트 케이스를 인스턴스화 할 때, 인자가 없는 기본 생성자를 호출 함으로, 적절한 파라매터를 이용하여 Super 클래스의 생성자를 호출하도록 구현해야 한다

  public SpinnerActivityTest() {
   
super("com.android.example.spinner", SpinnerActivity.class);
 
} // end of SpinnerActivityTest constructor definition

 테스트 대상 어플리케이션의 안드로이드 패키지 이름과 대상 Activity 클래스를 파라매터로 전달한다. 안드로이드는 이 정보를 이용하여, 실행한 테스트 대상 어플리케이션을 찾는다. 이제 여러분은 테스트 케이스 클래스에 테스트를 추가할 준비가 되었다.

Adding the setup method

 setUp() 메서드는 모든 테스트 수행 전에 호출된다. 여러분은 이 메서드를 변수를 초기화하고, 이전 테스트에서 일어났던 일들을 깔끔하게 정리하고자 할 때 사용할 수 있다. tearDown() 메서드를 사용할 수도 있는데, 이 메서드는 모든 테스트가 수행된 후애 매번 호출된다. 이 튜터리얼에서는 tearDown() 메서드를 사용하지는 않는다. 

여러분이 추가할 메서드는 다음과 같은 일을 수행한다.

  • super.setUp(). 상위 클래스의 setUp() 메서드를 호출한다. JUnit 에 요구되는 사항이다.
  • setActivityInitialTouchMode(false) 메서드를 호출한다. 디바이스 혹은 에뮬레이터의 터치 모드를 끔으로서, 테스트 어플리케이션이 키 이벤트를 전달 할 수 있도록 한다. 여러분은 반드시 Activity 를 시작하기 전에 터치 모드를 꺼야한다. 그렇지 않다면 이 메서드는 무시된다.
  • 시스템 오브젠트들의 참조 값을 저장한다. 테스트 대상 Activity 에서 사용된 Spinner 위젯과, SpinnerAdapter 그리고 어플리킹션이 처음 인스톨될 때 설정된 선택 문자열 값들에 관한 참조를 얻어와 저장한다. 이를 위해 다음과 같은 메서드가 호출된다. 
    • getActivity(). 대상 Activity 에 관한 참조를 얻어온다. 해당 Activity 가 현재 실행중이 아닌경우, 해당 Activity 를 실행한다. 
    • findViewById(int). Spinner 위젯에 관한 참조를 얻어온다.
    • getAdapter(). SpinnerAdpater 에 관한 참조를 얻어온다.

아래 코드를 SpinnerActivityTest 생성자 아래에 추가하라.

  @Override
 
protected void setUp() throws Exception {
   
super.setUp();
    setActivityInitialTouchMode
(false);
    mActivity
= getActivity();
    mSpinner
=
     
(Spinner) mActivity.findViewById(
        com
.android.example.spinner.R.id.Spinner01
     
);
      mPlanetData
= mSpinner.getAdapter();
 
} // end of setUp() method definition

그리고, 다음과 같은 멤버 변수를 추가하라.

  private SpinnerActivity mActivity;
 
private Spinner mSpinner;
 
private SpinnerAdapter mPlanetData;

그리고, 다음의 임포트를 추가하라.

import android.widget.Spinner;
import android.widget.SpinnerAdapter;

이제 여러분은 setUp() 메서드를 완성했다.

Adding an initial conditions test

 선결 조건 테스트는 테스트 대상 어플리케이션이 정상적으로 초기화되었는지를 테스트 한다. 이 테스트는 어떤 구체적인 점들을 테스트 하는 것은 아니며, 다음과 같은 점을 확인한다.

  • Item Select Listener 가 초기화되었는가? 이 Listener 는 Spinner 에서 아이템이 선택될 때 호출된다.

  • Spinner Adapter 가 초기화되었는가?

  • Spinner Adapter 가 올바른 숫자의 아이템을 갖고 있는가?

 실제 어플리케이션 초기화는 setUp() 에서 수행된다. 값들을 체크하는 일은 JUnit 의 Assert 메서드를 이용하여 이루어진다. 유용한 컨벤션으로 이 테스트 메서드의 이름은 testPreConditions() 가 사용된다. 

  public void testPreConditions() {
    assertTrue
(mSpinner.getOnItemSelectedListener() != null);
    assertTrue
(mPlanetData != null);
    assertEquals
(mPlanetData.getCount(),ADAPTER_COUNT);
 
} // end of testPreConditions() method definition

다음의 멤버를 추가하라.

 테스트 어플리케이션을 올바르게 인스턴스화하기 위해, 여러분은 테스트 케이스 클래스의 생성자를 추가해 주어야 한다. Test Runner 가 해당 테스트 케이스를 인스턴스화 할 때, 인자가 없는 기본 생성자를 호출 함으로, 적절한 파라매터를 이용하여 Super 클래스의 생성자를 호출하도록 구현해야 한다

public static final int ADAPTER_COUNT = 9;


Adding a UI test

 이제 Spinner 위젯에서 아이템을 하나 선택하는 UI 테스트를 작성해 보자. 이 테스트는 UI 에 키 이벤트를 전달한 후, 선택되는 아이템이 여러분이 기대하는 값과 일치하는지를 테스트 할 것이다.

 이 테스트는 여러분에게 안드로이드 테스팅에서 Instrumentation 이 얼마나 유용한지를 잘 보여줄 것이다. Instrumentation 에 기반한 테스트 클래스를 이용하는 경우에만, 여러분은 테스트 중인 어플리케이션에 키 입력이나 터치 입력 같은 UI 이벤트를 전달할 수 있다. Instrumentation 을 이용해, 여러분은 스크린 샷을 찍거나, 화면을 기록하거나, 직접 사람이 테스팅을 진행하지 않고도, UI 를 테스트 할 수 있다.

 Spinner 를 제어하기 위해서는, 우선 해당 위젯에 포커스를 요청한 후에, 아이템을 선택해야 한다. requestFocus() 와 setSelection() 을 이용할 수 있는에, 두 메서드 모두 테스트 중인 어플리케이션의 View 를 직접 접근하는 메서드이다. 따라서, 두 메서드를 호출할 때는 특별항 방법이 필요하다.

 테스트 어플리케이션에서 테스트 대상 어플리케이션의 View 와 상호작용하는 코드는 대상 어플리케이션의 UI 스레드 (메인 스레드)에서 동작해야한다. 이를 위해 여러분은 Activity.runOnUiThread() 메서드를 사용할 수 있다. runOnUiThread() 메서드의 인자로 익명의 Runnable 오브젝트를 사용하면서, Runnable 인터페이스의 run() 메서드를 오버라이드 하여, 원하는 테스트 코드를 추가할 수 있다.

 sendKeys() 메서드를 사용해 UI 어플리케이션에 키 이벤트를 전달할 수 있다. 안드로이드 Instrumentation 프레임워크가 키 이벤트를 테스트 대상 어플리케이션에 알아서 전달해 줌으로, 이 메서드는 굳이 UI 스레드 내에서 실행될 필요가 없다,

 테스트의 마지막은, Spinner 가 우리의 의도대로 동작하고 있는지, 미리 정해진 값과, 키 이벤트를 전달해서 선택된 값이 일치하는지를 확인하는 것이다.

실재 테스트를 위해 작성된 코드를 한 번 살펴보자.

1. 포커스를 얻고, 특정 아이템을 지정한다. public void testSpinnerUI() 라는 메서드를 추가한 후, 포커스를 요청하고 Spinner 의 특정 아이템을 선택한다. 이 코드는 어플리케이션의 UI 스레드에서 동작해야 한다. 

  public void testSpinnerUI() {

    mActivity
.runOnUiThread(
     
new Runnable() {
       
public void run() {
          mSpinner
.requestFocus();
          mSpinner
.setSelection(INITIAL_POSITION);
       
} // end of run() method definition
     
} // end of anonymous Runnable object instantiation
   
); // end of invocation of runOnUiThread

 다음과 같은 멤버 변수를 추가하라

  public static final int INITIAL_POSITION = 0;


2. 지정된 아이템을 선택한다. 아이템을 선택하기 위해 Spinner 에 키이벤트를 전달한다. 우선 Spinner 를 열기 위하여, 키패드의 가운데 버튼을 클릭하고, 아래쪽 방향키를 다섯번 클릭한 후에, 최종적으로 다시한번 키패드의 가운데 버튼을 클릭하여 아이템을 선택한다. 다음과 같은 코드를 추가하라.

    this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
   
for (int i = 1; i <= TEST_POSITION; i++) {
     
this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
   
} // end of for loop

   
this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);

다음과 같은 멤버 변수를 추가하라.

  public static final int TEST_POSITION = 5;

Spinner 의 최종적인 위치는 "Saturn" 을 가리키게될 것이다.

3. 결과를 확인하라. Spiiner 의 현재 상태 값을 요청한 후, 현재 선택된 값이 우리가 기대하던 값과 일치하는지 확인하라. getSelectedItemPostion() 메서드를 이용해 현재 선택된 아이템의 위치 값을 확인하고, getItemAtPostion() 메서드로 해당 위치의 아이템 값을 확인할 수 있다. Assert 메서드를 이용해 선택된 아이템이 우리가 기대하던 대로 "Saturn" 인지 확인 하라. 다음과 같은 코드르 추가하라.

    mPos = mSpinner.getSelectedItemPosition();
    mSelection
= (String)mSpinner.getItemAtPosition(mPos);
   
TextView resultView =
     
(TextView) mActivity.findViewById(
        com
.android.example.spinner.R.id.SpinnerResult
     
);

   
String resultText = (String) resultView.getText();

    assertEquals
(resultText,mSelection);

 
} // end of testSpinnerUI() method definition

다음과 같은 멤버 변수를 추가하라.

  private String mSelection;
 
private int mPos;

다음과 같은 임포트를 추가하라.

  import android.view.KeyEvent;
 
import android.widget.TextView;

  여기서 잠깐 멈춰, 테스트를 한번 실행해 보자. 테스트 어플리케이션을 동작하는 절차는 일반적인 안드로이드 어플리케이션을 동작하는 것과 약간의 차이가 있다. 여러분은 안드로이드 JUnit 어플리케이션으로 테스트 어플리케이션을 실행해야 한다. 관련 내용은, Running the Tests and Seeing the Results 항목을 참고하라.

 결과적으로, SpinnerActivity 어플리케이션이 실행되고, 테스트 어플리케이션이 해당 Activity 를 컨트롤 한다. 테스트 결과는 JUnuit 창에 출려된다, 출력되는 정보에 관한 내용은 Running the Test and Seeing the Results 항목에서 설명한다.


Adding state management tests

 SpinnerActivity 가 멈추거나(onPause) 종료될 때(onDestroy), 상태 정보를 잘 유지하는지 확인하는 테스트를 작성해보자. 이 경우, '상태 정보'란, Spinner 에서 현재 선택되어진 값을 의미한다. 사용자가 특정 아이템을 선택한 후, 어플리케이션이 멈추거나 종료되고, 그리고 다시 시작되는 경우 이전에 선택되어진 값이 그대로 유지되어야 한다.

 상태 정보를 유지하는 것은 중요하다. 사용자들은 현재 실행중인 어플리케이션에서 전화를 받기 위해 다른 어플리케이션을 옮겨 갔다가, 잠시 후 다시 돌아올 수 있다. 또, 안드로이드 시스템이 화면의 가로 세로모드가 변경됨에 따라 어플리케이션을 재 시작하기로 결정할 수도 있으며, 메모리를 확보하기 위해 백그라운드 어플리케이션을 종료할 수도 있다. 어떠한 경우이던간에, 사용자들은 기존 UI 정보가 보전되어 있기를 바랄 것이다.

SpinnerActivity 는 상태 정보를 다음가 같은 방식으로 관리한다.

  • Activity 가 화면 상에서 사라질 때: Activity 가 화면상에서 사라질 때, Spinner 의 아이템 위치와 그 값을 어플리케이션이 실행 중인 동안에 유지되는 형식으로 저장한다. 

  • Application 가 종료될 때:  Activity 가 종료되는 산군, Spinner 의 아이템 위치와 그 값을 영구적인 방법으로 저장한다. 

  • Activity 가 다시 화면상에 표시될 때:  이전 선택 값이 복구 된다.

  • Application 가 재 시작될 때: 이전 선택 값이 복구 된다. 

Note: 어플리케이션은 상태 값을 다른 방식으로 관리할 수도 있다. 하지만, 다른 방식에 관해서는 이 튜터리얼에서 다루지 않는다.

 Activity 가 화면에서 사라질 때, 'Paused' 되는 것이며, 다시 나타날 때, 'Resume' 된다. 이 것이 Activity 라이프 사이클의 핵심이며, Activity는 각각의 경우를 처리할 수 있도록, onPause() 와 onResume() 콜백을 제공한다. SpinnerActivity 는 두 콜백 함수를 이용하여 상태정보를 저장하고 복구한다. Activity 생명주기에 관한 보다 상세한 정보를 알고 싶으면, Activity Lifecycle 항목을 참고하라.

 첫 번째 테스트는 Spinner 의 선택 정보가 전체 어플리케이션이 종료된 후 재 시작되는 경우에도 유지되는지 확인한다. 이 테스트는 Instrumetation 을 이용하여, Spinner 의 값을 선택한 후, Activity.finish() 메서드를 이용하여 Activity 를 종료한다. 그리고 Instrumentation 의 getActivity() 메서드를 이용하여, Activity 를 재시작한다. 마지막으로, Assert 를 이용하여, Spinner 의 현재 값이 우리가 기대하는 값과 일치하는지 확인한다.

 두 번째 테스트는, Spinner 의 선택 정보가 Activity 가 일시 정지되었다가 다시 실행되는 경우에도 잘 유지되는지 확인한다. 이 테스트는 Instrumentation 을 이용하여, Spinner 의 값을 선택한 후, onPause() 와 onResume() 메서드를 호출한다. 마지막으로, Assert 를 이용하여, Spinner 의 현재 값이 우리가 기대하는 값과 일치하는지 확인한다. 

 이 테스트는 Activity 가 상태 정보를 관리하는 방법에대하여 아주 제한적인 가정만을 사용한다. Spinner 를 제어하기 위하여, Activity 의 메서드를 사용하고, onPause() 와 onResume() 메서드를 직접적으로 호출하긴 하지만, 그 외에는 Activity 를 하나의 '블랙박스' 로 간주하고 테스트를 진행한다.

Activity 가 종료되고 재시작되는 경우를 테스트 하기 위한 코드는 다음과 같다.

  1. testStateDestroy() 메서드를 추가한 후, Spiiner 의 특정 값을 선택한다.

      public void testStateDestroy() {
        mActivity
    .setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
        mActivity
    .setSpinnerSelection(TEST_STATE_DESTROY_SELECTION);

  2. Activity 를 종료한 후, 재 시작한다.

        mActivity.finish();
        mActivity
    = this.getActivity();

  3. Activity 로 부터 현재 Spinner 가 가리키는 값을 가져온다. 

        int currentPosition = mActivity.getSpinnerPosition();
       
    String currentSelection = mActivity.getSpinnerSelection();

  4. 현재 값과, 우리가 기대하는 값을 비교한다.

        assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
        assertEquals
    (TEST_STATE_DESTROY_SELECTION, currentSelection);
     
    } // end of testStateDestroy() method definition

    다음과 같은 멤버 변수가 필요하다.

      public static final int TEST_STATE_DESTROY_POSITION = 2;
     
    public static final String TEST_STATE_DESTROY_SELECTION = "Earth";

Activity 가 Pause 되고 Resume 되는 경우를 테스트하기 위한 코드는 다음과 같다.

1. testStatePause() 메서드를 추가한다.

    @UiThreadTest
   
public void testStatePause() {

@UiThreadTest 어노테이션은 안드로이드가 해당 메서드를 UI 스레드에서 실행 시 켜야 함을 나타낸다. 따라서, 이 메서드 내에서 Spinner 의 상태 값을 직접 변경할 수 있다. @UiThreadTest  메서드를 이용하면, 여러분은 필요한 경우 하나의 메서드 전체를 UI 스레드에서 동작하도록 설정 할 수 있다.

2. Instrumentation 을 설정 한다. 테스트 대상 어플리케이션을 컨트롤 하기 위한 Instrumentation 을 얻어온다.

    Instrumentation mInstr = this.getInstrumentation();


3. Spinner 의 특정 값을 선택한다.

    mActivity.setSpinnerPosition(TEST_STATE_PAUSE_POSITION);
    mActivity
.setSpinnerSelection(TEST_STATE_PAUSE_SELECTION);


4. Instrumentation 을 이용하여, ACtivity 의 onPause() 를 호출한다.

   mInstr.callActivityOnPause(mActivity);

테스트 중에, Activity 는 사용자 입력을 기다리는 중이다. callActivityOnPause() 메서드를 호출하면, UI 조작을 통해 Activity 를 정지 시킬 필요 없이 Activity 의 onPause() 메서드를 직접 호출 할 수 있다.

 

5. 강제로 Spinner 의 선택 값을 변경한다.

    mActivity.setSpinnerPosition(0);
    mActivity
.setSpinnerSelection("");

 이는, Activity 가 Resume 되는 경우 Spinner 의 상태 값을 그저 단순히 유지하고 있는 것이 아니라, 정말로 저장된 값으로 복원 하는지 확인하기 위해서이다.

6. Instrumentation 을 이용하여, onResume() 을 호출 하라.

    mInstr.callActivityOnResume(mActivity);

callActivityOnResume() 메서드를 호출하면, UI 조작을 통해 Activity 를 시작할 필요 없이 Activity 의 onResmume() 메서드를 직접 호출 할 수 있다.

 
7.Spinner 의 현재 상태 정보를 가져온다.

    int currentPosition = mActivity.getSpinnerPosition();
   
String currentSelection = mActivity.getSpinnerSelection();


8. Spinner 의 상태 정보가 우리가 기대하는 값과 일치하는지 확인한다.

    assertEquals(TEST_STATE_PAUSE_POSITION,currentPosition);
    assertEquals
(TEST_STATE_PAUSE_SELECTION,currentSelection);
 
} // end of testStatePause() method definition

다음과 같은 멤버 변수를 추가하라.

  public static final int TEST_STATE_PAUSE_POSITION = 4;
 
public static final String TEST_STATE_PAUSE_SELECTION = "Jupiter";

다음의 임포트를 추가하라.

  import android.app.Instrumentation;
 
import android.test.UiThreadTest;

Running the Tests and Seeing the Results

 SpinnerActivityTest 를 실행하는 가장 쉬운 방법은, Package Explorer 에서 직접 실행하는 것이다. 

1. Package Explorer 에서 마우스 오른쪽 버튼을 클릭한 후, Run As > Android JUnit Test 를 선택한다.



2. 에뮬레이터가 시작된다. 홈스크린이 잠겨 있는 경우, 잠금을 해체하라. (AVD 에서 선택한 API Level 에 따라 나타날 수도 그렇지 않을 수도 있다.)

3. 테스트 어플리케이션이 시작된다. Package Explorer 옆에 JUnit 을 위한 새로운 탭이 생성된다. 



이 화면은 두 개의 하위 패널로 이루어져 있다. 상위 패널은 테스트 결과를 요약해 보여주고, 하위 패널은 테스트가 실패한 원일을 보여준다. 

테스트가 성공적으로 수행된 경우, 아래와 같은 화면이 보일 것이다.

테스트 결과는 아래와 같이 요약된다.

  • 테스트 수행에 걸린 총 시간 
  • 수행된 모든 테스트 수(Runs) - 전체 테스트 클래스에 포함되어 있는 모든 테스트의 숫자.
  • 테스트 중 발생한 오류 수(Errors) - 테스트 중에 발생한 오류 및 예외상황. 
  • 테스트 중 발생한 실패 수(Failures) - assert 로 인해 실패한 테스트 수
  • 프로그레스 바 - 테스트 진행에 따라 왼쪽에서 오른쪽으로 차오르며, 모든 테스트가 성공하면 녹색으로 그렇지 못할경우 빨간색으로 남는다.

 화면 상단 본문 부분에는 테스트 수행에 관한 상세한 정보가 포함된다. 수행된 각각의 테스트에 관하여 클래스 이름를 확인 할 수 있다. 이를 클릭하면, 각각의 테스트 메서드 호출에 따른 결과와 코드 라인을 확인 할 수 있다. 만일 테스트 메서드를 더블클릭하면, 해당 소스를 에디터 View 에 표시한 후, 해당 메서드 위치로 포커스를 옮겨 준다. 

 화면 하단은 스택 트레이스(Stack Track) 정보를 표시한다. 화면 상단에서 실패한 테스트를 선택하면, 화면 하단은 해당 테스트에관한 스택 트레이스 정보를 표시해 준다. 이에 관한 내용은 다음 항목에서 설명한다.

Forcing Some Tests to Fail

 테스트는 성공하는 것 만큼이나 실패하는경우에도 유용하다. 이 항목에서는, 테스트가 실해하는 경우 Eclipse ADT 에서 어떤 일이 일어나는지에 관해 살펴볼 것이다. 여러분은 실패한 테스트 케이스 클래스를 재빠르게 확인 하고 실패한 메서드를 찾을 수 있으며, 정확한 원인을 파익하기 위하여 스택 트레이스 정보를 활용할 수 있다.  

 예제에 사용된 SpinnerActivity 어플리케이션은 SpinnerActivityTest 의 모든 테스트를 통과한다. 강제로 테스트가 실패하도록 하기 위하여, 예제 어플리케이션의 코드 일부분을 수정해 보자. 어플리케이션 셋업 부분의 코드를 수정하여, testPreConditions() 과 testTextView() 테스트가 실패하도록 할 것이다.

강제로 테스트를 실피하도록 하기 위해, 다음과 같이 해보자.

1. SpinnerActivity 프로젝트의 SpinnerActivity.java파일을 연다.

2. SpinnerActivity.java, 의 onCreate() 메서드 마지막 라인을 확인하라.
    // mySpinner.setOnItemSelectedListener(null);

주석 처리를 풀자. ItemSelectedListener 가 null 값으로 설정된다.

    mySpinner.setOnItemSelectedListener(null);


3. 테스트 어플리케이션 내의 testPreConditions() 메서드는 다음과 같은 테스트를 포함하고 있다. "assertTrue(mSpinner.getOnItemSelectedListener() != null)" 여러분이 어플리케이션 코드를 수정했기 때문에, 해당 테스트는 실패할 것이다.

4. 테스트를 실행한다.

JUnit 뷰에 테스트 결과가 출력될 것이다. 이번에는 붉은 색 프로그래스 바를 볼 수 있다. 실패 횟수는 2개이며, 테스트 목록 옆에 작은 'X' 아이콘이 표시될 것이다. 이 아이콘은 실패한 테스트를 나타낸다. 화면은 아래와 유사할 것이다.


이제, 테스트가 실패된 원인을 확인해 보자.

1. Failure Trace 라는 제목을 갖은 하단 패널에, 실패한 테스트의 스택 트레이스 정보를 확인할 수 있다. 아마 아래의 스크린 샷과 유사할 것이다.



2. 트레이스의 첫 번째 라인이 에러를 나타낸다. 이 경우, JUnit 의 Assert Failure 이 발생 하였다. 해당 구문을 더블 클릭하면, 테스트 코드를 담고 있는 Eclipse 의 Edit View 에서 새로운 윈도우가 열린다. 또한 실패한 Assert 구문이 하이라이트 되어 있다.

 Assert Failure 은 메인 어플리케이션이 ItemSelectedListener 를 Null 로 설정했기 때문에 발생하였다. 

 원한다면 여러분은 testTextView 의 실패 원인에 관해서도 살펴볼 수 있다. 한가지 기억할 점은, testPreConditions 은 테스트 대상 어플리케이션의 초기 설정에 관한 테스트임으로, 이 테스트가 실패 했다면, 이어지는 테스트의 결과는 신뢰할 수 없다는 점이다. 이런 경우, 최적의 방법은 해당 문제를 우선 해결한 후, 테스트는 다시 수행하는 것이다.

 SpinnerActivity.java 파일로 돌아가서, 코드를 수정했던 부분을 다시 주석 처리 한 테스트를 수행해 보라.

 이제 여러분은 모든 튜터리얼을 완료 하였다. (주> 번역도 이제 막바지라는!!)

Next Steps

 이 예제는 여러분이 어떠헤 테스트 프로젝트를 생성하고, 테스트 클래스를 만들고, 테스트 케이스를 추가혀며,어떻게 UI 와 상태 정보에 관한 테스트를 수행할 수 있는지에 관해 이야기 했다. 여러분이 이런 핵심적인 사항이 익숙하다면, 다음과 같은 추가적인 내용을 살펴볼 필요가 있다. 

안드로이드 테스팅에 관해 좀 더 알기

  • Testing Android Applications 문서는 안드로이드 테스트가 어떤식으로 동작하는가에 관한 전반적인 내용을 다루고 있다. 여러분이 이제 막 안드로이드 테스팅에 발을 들여 놓았다면, 위 문서를 살펴보면 도움이 될 것이다.

안드로이드에서 사용가능한 Test Class 에 관해 좀 더 알기

안드로이드 Assert 와 Utility 클래스에 관하여 좀 더 알기

  • Assert, JUnit Assert 클래스

  • MoreAsserts,  안드로이드에서 추가적으로 제공되는 Assert 클래스

  • ViewAsserts, View 를 테스트 하는데 유용한 Assert 클래스

  • TouchUtils, Activity 의 터치 이벤트를 시뮬레이션 하는데 유용한 메서드들

안드로이드 Instrumentation 프레임워크에 관해 좀 더 알기