Android/App개발2011. 6. 16. 18:31

안드로이드 개발시 유닛테스트를 하는 방법에 대해 정리한다.

두가지 케이스정도로 정리해보면
 1. 액티비티 테스트 : UI 테스트 자동화
 2. 클래스 테스트 : 로직을 담고 있는 클래스들을 테스트 자동화

방법은 Android project를 만들 때 Android Test Project로 만든다. 이 때 테스트하고자 하는 앱을 지정하게 되어 있다. 
 
1. 액티비티 테스트 
 -  ActivityInstrumentationTestCase2를 확장
public class HelloActivityTest extends ActivityInstrumentationTestCase2<HelloActivity> 
 - 일부 메소드 override
protected void setUp()
protected void tearDown()
 - test 메소드들 구현
 - Activity에 접근은 this.getActivity()로 가능하다. 

2. 클래스 테스트
 - InstrumentationTestCase 를 확장
 
 public class MetadataManagerTest extends InstrumentationTestCase 
 - 일부 메소드 override
protected void setUp()
protected void tearDown()
 - test 메소드들 구현
 
AsyncTask를 테스트할 때는 CountdownLatch를 이용해서 비동기 스레드를 테스트 할 수 있다.
출처 : http://kingori.egloos.com/4554640
public class AsyncTaskTest extends InstrumentationTestCase {

  public void testAsyncTask() {
    final Data data = new Data();
    final CountDownLatch signal = new CountDownLatch(1); // countdown 1을 기다리는 랫치

    Callback a = new Callback() {
      onPrepare() { data.val = 1; }
      onSuccess() { data.val = 2; signal.countDown();} // 작업이 끝나면 1을 내려 유닛테스트 쓰레드가 진행되게 한다.
      onFail() { data.val = 3; signal.countDown(); } // 실패할 수도 있으니..
    }

    runTestOnUiThread( new Runnable() {
      public void run() {
        Manager.getInstance().doSomething(a);
      }
    });
    signal.await(); // 랫치 값이 0이 될 때 까지 대기 상태
    assertEquals( data.val, 2 ); //결과 확인
  }
}

Posted by 삼스
Android/App개발2011. 6. 10. 13:08
http://www.hanb.co.kr/network/view.html?bi_id=1033
 

저자:
 김대곤

Java Native Interface에 관련된 서적을 찾으려고 아마존을 뒤적일 때, 다음과 같은 서평을 보게 되었다. “한 가지 분명한 사실은 JNI를 쓰지 않는 것이 훨씬 좋다는 것이다. JNI는 다루기 힘들고, 불안정하며, 에러가 발생할 소지가 많으며, 무엇보다도 JNI가 쏟아내는 Runtime 에러는 자바 가상 머신을 날려 버릴 수 있는 사악한 기술이다.” 이 서평을 쓴 사람은 그 책을 사서 봤을 것이며, 단지 관심에서가 아니라 심각하게 Java Native Interface를 사용했을 것이며, 아마도 사용하고 있을 것이다. JNI는 자주 사용되지는 않지만, 그렇다고 불필요한 기술도 아니다. 아니 절대적으로 필요한 기술임이 틀림없다.

Java Native Interface는 자바에서 다른 언어로 개발된 기능을 내부적으로 사용할 수 있게 해 준다. 이미 C 언어나 C++로 개발된 기능이 있다면 그것을 자바 프로그램 안에서 사용할 수 있게 해 준다. 이 글은 JNI을 사용하는 두가지 간단한 예제를 통해 JNI가 무엇이며, JNI를 어떻게 사용하는지에 대해 간단히 살펴볼 것이다. 첫 번째 예제는 항상 등장하는 HelloWorld식 예제이고, 두 번째는 C 언어로 개발된 정렬 프로그램을 자바 프로그램에서 사용하는 예제이다. 

썬 자바 사이트에는 JNI를 구현하는 여섯가지 단계를 설명하고 있다.
1. Native 메소드를 호출하는 자바 코드 작성.
2. 첫 단계에서 쓴 자바 코드의 컴파일.
3. javah 명령어를 사용하여 헤더 파일의 생성.
4. 지정된 언어로 호출되는 Native 메소드를 구현.
5. Native 메소드를 구현한 코드를 동적 라이브러리로 컴파일.
6. 실행.

Native 메소드를 호출하는 자바 코드에는 세 가지 요소가 포함된다. Native 메소드의 선언, 실제 Native 메소드의 사용, 동적 라이브러리를 시스템 상에 상주하도록 불러오는 것이다. 이 세 가지는 아래 코드에서 강조되어 있다.


[HelloWorld.java]
/*
 * Created on 2004. 12. 31.
 * File name : HelloWorld.java
 * @author DaeGon Kim
 *
 */
public class HelloWorld {

              String greeting = "Hello Java World";

              public native void display();

              public static void main(String[] args) {

                            HelloWorld hw = new HelloWorld()
              System.out.println(hw.greeting);
                            hw.display();
                            System.out.println(hw.greeting);

              }

              static {
                            System.load("D:\\Project\\Java\\hello.dll");
              }

}


두 번째와 세 번째 단계에서는 javac 명령어로 HelloWorld.java를 컴파일한 후, javah 명령어로 헤더 파일을 생성한다. 
> javac HelloWorld.java
> javah HelloWorld
javah 명령을 실행하면, 아래에 보이는 HelloWorld.h가 생성되며, 이 파일을 수정하지 않는다.


[HelloWorld.h]
/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    display
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_display
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif


헤더 파일에서 강조된 부분은 실제 C 언어로 구현되어야 하는 함수이다. JNIEXPORT와 JNICALL과 같은 자바와 관련된 용어의 등장에서 알 수 있듯이, 헤더 파일에 정의된 함수를 구현하는 C 파일을 컴파일하기 위해서는 자바 시스템에서 포함되어 있는 파일이 필요하다. 환경설정에 관한 설명은 C 파일의 컴파일 단계에서 설명하기로 하고, C 언어로 위 함수를 구현해 보자.


[HelloWorld.c]
#include 
#include "HelloWorld.h"
#include 


JNIEXPORT void JNICALL Java_HelloWorld_display(JNIEnv *env, jobject this) {

    jfieldID       fldid;
    jstring        greeting;
    const   char   *temp;

    // 넘겨지는 객체 this의 클래스를 가져온다.   
    jclass instance = (*env)->GetObjectClass(env,this);

    printf("   Start of C implementation.\n");
    
    if ( this == NULL ) {
        fprintf(stderr, "Input pointer is null!");
        return;
    }
    
    // 클래스 안에서 greeting이라는 속성이 가진 위치를 가져온다.
    fldid = (*env)->GetFieldID(env, instance, "greeting", "Ljava/lang/String;");
    
    if ( fldid == NULL ) {
        fprintf(stderr, "Failed to get the field id\n");
        return;
    } 
    
    // 실제 객체에서 fldid에 있는 값을 읽어온다.
    greeting  = (*env)->GetObjectField(env, this, fldid);
    // 일어온 값을 C 데이터로 변환한다.
    temp      = (*env)->GetStringUTFChars(env, greeting, NULL);
   
    printf("      %s \n", temp);

    // 자바의 String 필드에 저장할 값을 만든다.
    greeting = (*env)->NewStringUTF(env, "Hello C World");
   
    if ( greeting == NULL ) {
        printf("Out of memory\n");
        return;
    }
    
    // greeting에 저정돤 값을 자바 객체로 전달한다.
    (*env)->SetObjectField(env, this, fldid, greeting);

    printf("   End of C implementation \n");
   
    return;

}


이제 필요한 모든 파일을 갖추어졌다. 이제 컴파일하고 실행하는 일만 남아 있다. 컴파일 하기 위해서는 몇 가지 설정이 필요하다. 앞으로 기술되는 설정은 윈도우를 기준으로 하고 있고, 마이크로 소프트 C 컴파일러를 쓴다는 가정하에서 기술되었다. 먼저, jni.h가 필요하다. 이 파일은 자바 홈디렉토리(이하 JAVAHOME) 아래 include 디렉토리에 있으며, 그 아래에 있는 win32 디렉토리도 include에 포함되어 있어야 한다.

set INCLUDE=%JAVAHOME%\include;%JAVAHOME%\include\win32;%INCLUDE%

LIB 속성을 위해서는 JAVAHOME 아래에 있는 lib 디렉토리가 포함되어야 한다.

set LIB=%JAVAHOME%\lib;%LIB%

위에서 설명한 설정은 JNI를 사용하게 됨에 따라 추가적으로 해야 하는 설정이며, Command Prompt에서 VC7 컴파일러를 실행하고자 할 때는 다음의 설정이 추가적으로 필요하다.

INCLUDE : VC 홈 디렉토리 아래에 있는 include 디렉토리
LIB : VC 홈 디렉토리 아래에 있는 lib 디렉토리
PATH : Studio 홈 - Common7 아래에 있는 IDE, VC 홈 디렉토리 아래에 있는 bin 디렉토리.

이제 남은 일은 HelloWorld.c를 컴파일하고 HelloWorld를 실행하는 일만 남았다.

cl HelloWorld.c -Fehello.dll -MD -LD

위의 명령어를 실행하면 hello.dll 파일이 생성된다. 그리고 나서 HelloWorld 클래스를 실행하면 다음과 같은 결과를 얻게 될 것이다. 


Hello Java World
   Start of C implementation.
      Hello Java World
   End of C implementation
Hello C World


이것으로 첫 번째 예제가 성공한 것이다. 이 예제는 일반적으로 JNI가 사용되는 방식과 거리가 있어 보인다. JNI는 개발 중인 자바 어플리케이션이 있으며, 이미 개발된 Native 라이브러리가 존재한다. 두 번째 예제에서는 기존에 C 언어로 개발된 정수 정렬 함수를 자바에서 사용하는 예제이다. 

먼저 Sort.c를 살펴보자. 흔히 볼 수 있는 C로 구현된 Insertion 정렬 함수이다.


[Sort.c]
#include 
#include 

int *sortIntegers(int *A, int n) {
    
    int i   = 0;
    int j   = 0;
    int key = 0;
    
    for ( j=1; j < n ; j++ ) {
        key = A[j];
        i = j - 1;
        while ( i >=0 && A[i] > key ) {
            A[i+1] = A[i];
            i--;
        }
        A[i+1] = key;
    }
    
    return A;
    
}


Sort.h 에는 Sort.c 안에 구현된 함수의 리스트가 나열되어 있다.


[Sort.h]
int *sortIntegers(int *A, int n);


이제 이 함수의 기능을 사용하는 Sorting.java 를 작성하자.


[Sorting.java]
public class Sorting {

    public static native int[] sort(int[] A, int n);
    
    public static void main(String args[]) {
        
        int Input[] = {3,4,7,2,5,6,9,11,1,8,10};
        
        int sorted[] = sort(Input, Input.length);
        
        for (int i=0; i < sorted.length ; i++ ) {
            System.out.println("A[" + i + "] = " + sorted[i]  );
        }
    }
    
    static {
        System.load("D:\\Project\\Java\\sorting.dll");
    }
    
}


Sorting.java를 컴파일 하고, Sorting 클래스를 사용하여 Sorting.h 를 생성한다. 이제 남은 일은 HelloWorld.c 와 같은 입력값과 반환값을 전달해주는 C 프로그램이다. 


[Sorting.c]
#include 
#include "Sorting.h"
#include "Sort.h"
#include 

JNIEXPORT jintArray JNICALL Java_Sorting_sort(JNIEnv *env, jclass cl, jintArray arr, jint n) {

    jint       *A   = NULL;
    jintArray  temp = NULL;
    
    A = (*env)->GetIntArrayElements(env, arr, NULL);
    
    if ( A == NULL ) {
        fprintf(stderr, "Failed to get int array\n");
        return arr;
    }
    
    A = sortIntegers(A, n);
    
    temp = (*env)->NewIntArray(env, n);
    
    (*env)->ReleaseIntArrayElements(env, temp, A, 0);
    
    return temp;

}


Sorting.c 를 컴파일하자. 이 때 다음과 같은 에러가 발생한다면, 

“LINK : warning LNK4098: defaultlib 'LIBC' conflicts with use of other libs; use /NODEFAULTLIB:library”

Sorting.c 와 Sort.c가 다른 방식으로 컴파일 되었기 생긴 에러이다. Sort.c를 MD 옵션으로 다시 컴파일하면, 정상적으로 컴파일될 것이다. 이제 Sorting 클래스를 사용할 수 있다. Sorting 클래스를 실행하면 다음과 같은 출력을 보게 될 것이다.

A[0] = 1
A[1] = 2
A[2] = 3
A[3] = 4
A[4] = 5
A[5] = 6
A[6] = 7
A[7] = 8
A[8] = 9
A[9] = 10
A[10] = 11


필자는 여기에서 어떻게 Wrapper 프로그램을 작성하는지에 대해서 설명하지 않았다. 하지만 앞에서 다룬 두 예제에는 Primitive 타입인 int, 배열, 객체를 다루었기 때문에 각 경우에 해당되는 함수 리스트만 있으면, 프로그램에 별 어려움이 없으리라 생각된다. 다음 기사에는 JNI가 자바 가상 머신을 날려버려 전체 자바 어플리케이션이 다운되는 것을 피해가는 방법에 대해서 살펴보도록 하자.
Posted by 삼스
Android/App개발2011. 6. 3. 16:42
http://sites.google.com/site/endihom/home/programming-language/android/article/listview-backgrounds

ListView 는 안드로이드에서 가장 널리 사용되는 위젯이다. 사용하기 쉽고, 매우 유연하며 강력하다. ListView는 가끔 이해하기 어려울수 있다.

ListView에서 나타나는 가장 공통된 이슈중에 하나는 커스텀 백그라운드를 사용하고자 할때이다. 다른 안드로이드 위젯처럼 디플트로 ListView는 반투명 배경을 가질수 있는데 이는 개발자가 매우 어두운 회색(현재 다크 테마에서는 #FF191919)디폴트 윈도우 배경을 통해 볼수 있다는 것을 뜻한다. 게다가 ListView는 fading edges를 기본적으로 제공한다. 아래의 스크린샷의 윗 부분을 보면 첫번째 아이템이 점차 검은색으로 페이드 되는것을 알수 있다. 이 기술은 스크롤되는 컨테이너를 가지는 시스템에서 전반적으로 사용된다.

Android's default ListView

패이드 효과는 Canvas.saveLayerAlpha() 와 Porter-Duff Destination Out blending 모드의 조합으로 구현되어 진다.

불행하게도, ListView에서 커스텀 배경을 사용하거나 윈도우의 배경을 변경할때 화면이 이상하게 보일 것이다. 아래 두개의 스크린샷은 윈도우 배경을 변경했을때 어플리케이션에서 어떤일이 일어나는지를 보여준다. 첫번째 화면은 디폴트로 리스트가 어떡해 보여지는지를 볼수 있고 두번째 화면은 터치 제스쳐를 통해 초기화되어 스크롤되는 동안의 모습이다.

Dark fade Dark list

이 랜더링 이슈는 안드로이드 프레임워크가 ListView의 모든 인스턴스에 기본적으로 적용된 최적화 기법에 의한 것이다. 앞서 언급한데로 페이드 효과는 Porter-Duff blending mode를 사용해 구현되었다. 이 구현방법은 정말 잘 동작하지만 화면에 보이지 않는 비트맵에서 렌더링의 일부분을 캡쳐해야 함으로서 매우 고비용이며 드로잉 성능을 저하시킬수 있다. 그래서 추가적인 blending(메모리로부터 다시 읽어 들이는 것을 의미)이 필요하다 

ListView는 거의 대부분 고정 배경화면에서 디스플레이되기 때문에 고비용이 드는 작업으로 내려갈 이유가 없다. 이것이"cache color hit"라 불리는 최적화를 소개하는 이유다. 캐쉬 컬러 힌트는 윈도우의 배경색에 기본적으로 설정되어 있는 RGB색으로 안드로이드 dark테마에서 #191919이다. 이 힌트가 설정되면 ListView(사실 이 클래스의 부모 클래스)는 단일 배경화면으로 그릴 것이므로 고비용의 saveLayerAlpha()와 Porter-Duff 렌더링을 단순한 그레디언트로 대체할수있다는 것을 알게 된다. 이 그레디언트는 완전히 투명한 컬러에서 캐시 컬러 힌트 값까지 그레디언트 되고 이미지 위에서 리스트 바닥에 dark 그레디언트된 보고자 하는 바로 그것이다.

앞서 언급한데로 Listview와 안드로이드 UI 툴킷의 모든 기본 위젯들은 기본적으로 투명/반투명 배경을 가지진다. 이것은 ListView가 child를 다시 그릴때 윈도우 배경과 child를 반드시 혼합(blend)해야한다는 것을 뜻한다. 다시 한번 말하지만, 이것은 고비용의 메모리 다시 읽기를 초래하며 이 메모리 다시 읽기는 초당 수십번의 다시 그리기가 발생하므로 스크롤이나 플리핑하는동안 성능이 매우 떨어지게된다.

스크롤하는 동안 그리기 성능을 향상 시키기 위해 안드로이드 프레임워크는 cache color hint를 재사용한다. 이 힌트가 설정되면 프레임워크는 힌트값(scrolling cache라는 또다른 최적화 기법이 적용되지 않은 상태라고 가정하자)으로 채워진 비트맵안 리스트의 child를 복사한다. 그럼 다음 ListView는 화면에 비트맵을 직접 그린다. 비트맵은 불투명하므로 혼합(blending)이 필요하지 않다. 기본 cache color hint는 #191919이므로 스크롤 되는 동안 각 아이템의 뒤에서 dark 배경색을 볼수 있다.

만약 단일 배경색을 가지지 않거나 hint를 적절한 단일 컬러 값으로 설정한다면 이 이슈를 해결하기 위해 cache color hint 최적화 방법을 꺼 두어야 한다. 코드(setCacheColorHint(int) 참고)나 XML을 적절히 사용(android:cacheColorHint 속성)하여 이 작업을 수행할 수 있다. 최적화를 꺼두기 위해 단순히 투명색 #00000000을 사용하라. 아래의 화면은 XML 레이아웃 파일에 android:cacheColorHint="#00000000"으로 설정한 리스트를 보여준다.

Fade on a custom background

보는것 처럼 나무느낌의 배경화면에서 페이드 효과는 완벽하게 동작한다. cache color hint 기능은 최적화가 어떤 상황에서 개발자를 매우 힘들게 할수도 있으므로 흥미롭다. 하지만 이 특별한 상황에서 기본 동작의 이점은 증가된 복잡도 보다 더 크다.


Posted by 삼스