2013년 3월 10일 일요일

unit test에 대한 생각


급한 프로젝트를 진행하면서 항상 갈등에 빠지는 것이 있습니다.

unit test를 만드느냐, 만들지 않느냐, 아니면, 어느정도 까지 만드느냐...
보통은 위와 같은 갈등을 하다가... 바쁘니까(^^)  주어진 시간이라는 리소스를 설계와 개발에 집중을 하기 위해서 다음을 기약합니다.

그런데 프로젝트를 진행하다보면 unit test를 만들어 뒀으면 좋았을 경우를 자주 만나게 됩니다.

  1. 다른 사람 또는 다른 팀에서 전달해 주는 라이브러리를 사용하는 경우 해당 라이브러리를 사용하기 전에 라이브러리를 체크하기 위해 각 인터페이스에 대한 unit test를 만들어 두었다면,
    1. 해당 라이브러리에 의해 발생할 수 있는 다양한 삽질을 미연에 방지하여 리소스를 절약 할 수 있습니다.
    2. 라이브러리 배포 시점에 간단하게 한번만 돌려서 이상 여부를 리포트 할 수 있습니다. 이또한 리소스 절약이 될 수 있네요.
    3. 만약 사용하는 사람이 n명 이라면 절약된 리소스 * n 정도가 절약 될 수 있겠네요.
  2. UI시나리오 별로 unit test를 만들어 두었다면,
    1. 프로그램의 형상을 유지하기 쉽다. 즉, 수정작업에 의해 발생할 수 있는 side-effect를 unit test에 의해 막을 수 있습니다.(물론, 어느정도의 unit test를 구성해 뒀느냐에 대한 차이는 있습니다.^^)
      1. QA검증을 할 수 있는 프로젝트 환경이라면, 더 큰 필요성이 느껴집니다. 그 이유는 side-effect에 대한 비용이 너무나도 크기 때문입니다. 비용은 아래와 같습니다.
        1. QA인력이 테스트 하여 이슈로 등록한다.
        2. (여러 프로젝트가 같이 진행되는 환경이라면) 프로젝트 관리자들에 의해 해당 이슈가 누구의 이슈인지 구분되어야 한다.
        3. 구분된 이슈는 각 프로젝트 개발자가 확인하여 해당 이슈가 자신의 이슈인지 다른 프로젝트의 이슈인지 확인해야 한다.
        4. [반복] 중간 중간 QA인력 또는 프로젝트 관리자에 의해 이슈가 어떤 상태인지 관리가 되어야 한다.
        5. [반복] 관리되는 이슈는 weekly 회의와 같은 회의를 통해 보고되고, 논의되어야 한다.
    2. UI시나리오에 대한 설계서를 조금더 꼼꼼하게 검토해서 구현하게 되는 부가적인 이점을 가질 수 있습니다.
      1. QA검증 이슈에 등록되지 않아서 위에서 언급한 2-1-1에서 발생하는 리소스를 절약할 수 있습니다.
    3. 배포 이전에 개발자가 수행할 수 있는 레벨의 테스트를 자동으로 수행할 수 있다.

우선은 생각나는 대로 unit test를 만들어 두었다면 좋았을 만한 점들을 정리해 보았습니다.

사실은 정리된 점들 뿐아니라 자신이 만든 프로그램에 대한 자신감, 애착을 가질 수 있고, 개발 초기단계 부터 unit test를 생각하면서 설계에 들어 간다면, 테스트를 위해 각 모듈이 분리되는 것에 의해 프로그램이 자연스럽게 커플링이 줄어드는 설계 및 구현이 될 수 있다라는 장점도 가지게 됩니다.

  마지막으로 unit test에 대한 제 생각을 정리해보겠습니다.
프로토타입을 통한 데모를 보여줘야 하는 프로그램이 아니라면, 전체 프로젝트의 리소스 절약차원에서 반드시 필요합니다. 그렇다고, (경험상) 프로젝트 관리자가 unit test를 위한 일정을 주려고는 하지 않습니다. 따라서, 개발자가 자신의 시간을 쪼개어 unit test 에 대한 경험을 조금씩 늘여 가면서 unit test set을 구성하는 시간을 줄여 가는 노력이 필요합니다. 그렇게 경험을 쌓아 가다보면 짧은 시간에 unit test를 구성할 수 있는 개발자 개개인의 역량이 쌓일 것이고, 그런 역량은 개발자로서의 자기만족과 함께 자연스레 더나은(?) 개발자가 될 수 있는 지름길이 될 것입니다.


2013년 2월 27일 수요일

SharedPreference의 사용범위(access range)

결과 부터 말하면, 사용범위는 one-process(MODE_PRIVATE에 의해)와 multi-process(MODE_MULTI_PROCESS에 의해) 입니다.

참고 사항

  1. MODE_MULTI_PROCESS는 Gingerbread(Android2.3)까지는 always on이었으나, 이후 버전 부터는 기본적으로 off되었다고 하니, 여러개의 process에서 공유하려면, MODE_MULTI_PROCESS를 명시적으로 이용하여 getSharedPreferences()를 이용해야 겠네요.
  2. MODE_WORLD_READABLE과 MODE_WORLD_WRITEABLE은 위험해서 deprecated(API level17에서) 되었습니다.


public abstract SharedPreferences getSharedPreferences (String name, int mode)

Added in API level 1
Retrieve and hold the contents of the preferences file 'name', returning a SharedPreferences through which you can retrieve and modify its values. Only one instance of the SharedPreferences object is returned to any callers for the same name, meaning they will see each other's edits as soon as they are made.
Parameters
nameDesired preferences file. If a preferences file by this name does not exist, it will be created when you retrieve an editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
modeOperating mode. Use 0 or MODE_PRIVATE for the default operation, MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE to control permissions. The bit MODE_MULTI_PROCESS can also be used if multiple processes are mutating the same SharedPreferences file. MODE_MULTI_PROCESS is always on in apps targetting Gingerbread (Android 2.3) and below, and off by default in later versions.
Returns
  • Returns the single SharedPreferences instance that can be used to retrieve and modify the preference values.




public static final int MODE_PRIVATE

Added in API level 1
File creation mode: the default mode, where the created file can only be accessed by the calling application (or all applications sharing the same user ID).
Constant Value: 0 (0x00000000)

public static final int MODE_WORLD_READABLE

Added in API level 1
This constant was deprecated in API level 17.
Creating world-readable files is very dangerous, and likely to cause security holes in applications. It is strongly discouraged; instead, applications should use more formal mechanism for interactions such as ContentProvider, BroadcastReceiver, and Service. There are no guarantees that this access mode will remain on a file, such as when it goes through a backup and restore. File creation mode: allow all other applications to have read access to the created file.
Constant Value: 1 (0x00000001)

public static final int MODE_WORLD_WRITEABLE

Added in API level 1
This constant was deprecated in API level 17.
Creating world-writable files is very dangerous, and likely to cause security holes in applications. It is strongly discouraged; instead, applications should use more formal mechanism for interactions such as ContentProvider, BroadcastReceiver, and Service. There are no guarantees that this access mode will remain on a file, such as when it goes through a backup and restore. File creation mode: allow all other applications to have write access to the created file.
Constant Value: 2 (0x00000002)

2013년 1월 14일 월요일

What is reflection of Java?

The Java reflection API allows you to do exactly that by giving you a window into the fundamental features of the language -- classes and fields and methods -- via an ordinary Java API.


Refer to this below link
  in English. Reflections on Java Reflection
  in Korean. Java 리플렉션에 대한 재고

Using reflection with Android

Use reflection to find such below symbol.

  • class and its information
  • inner class and its information
  • member field and its information
  • member method and its information

Sample Code


import java.lang.reflect.Field;


/**
 * Helper class for Java reflection
 *
 * @author tnkim
 *
 */
public class ReflectHelper {

    /**
     * Return flag that expresses existing a field or not
     *
     * @param clsName class name that is included package name
     * @param fldName field name
     * @return
     */
    public static boolean hasField(String clsName, String fldName) {
        Class<?> cls = ReflectHelper.getClass(clsName);
        if (cls == null)
            return false;

        Field fieldlist[] = cls.getDeclaredFields();
        for (Field fld : fieldlist) {
            if (fld.getName().equals(fldName))
                return true;
        }

        return false;
    }

    /**
     * Return flag that expresses existing the specific field
     * or not in the specific inner class
     *
     * @param clsName class name that is included package name
     * @param fldName field name
     * @return
     */
    public static boolean hasFieldInInnerClass(String clsName, String innerClsName, String fldName) {
        Class<?> cls = ReflectHelper.getInnerClass(clsName, innerClsName);
        if (cls == null)
            return false;

        Field fieldlist[] = cls.getDeclaredFields();
        for (Field fld : fieldlist) {
            if (fld.getName().equals(fldName))
                return true;
        }

        return false;
    }

    /**
     * Return flag that expresses existing the specific inner class
     * or not in the specific class
     *
     * @param clsName class name that is included package name
     * @param fldName field name
     * @return
     */
    public static boolean hasInnerClass(String clsName, String innerClsName) {
        Class<?> innerCls = getInnerClass(clsName, innerClsName);
        if (innerCls == null)
            return false;

        return true;
    }

    /**
     * Return a Class for the specific clsName.
     *
     * @param clsName
     * @return
     */
    public static Class<?> getClass(String clsName) {
        Class<?> cls = null;
        try {
            cls = Class.forName(clsName);
        } catch (ClassNotFoundException e) {
            cls = null;
        }
        return cls;
    }


    /**
     * Return an inner class for the specific clsName
     * and the specific innerClsName.
     *
     * @param clsName
     * @param innerClsName
     * @return
     */
    public static Class<?> getInnerClass(String clsName, String innerClsName) {
        Class<?> cls = ReflectHelper.getClass(clsName);
        if (cls == null)
            return null;

        StringBuilder builder = new StringBuilder(clsName);
        builder.append('$').append(innerClsName);
        String clsNInnerClsName = builder.toString();
        Class<?>[] classList = cls.getClasses();
        for (Class<?> tmpCls: classList) {
            if (tmpCls.getName().equals(clsNInnerClsName)) {
                return tmpCls;
            }
        }

        return null;
    }
}

2012년 12월 28일 금요일

System popup이 떠 있을 때 Activity가 volume설정 시점을 확인하는 방법

볼륨관련 UI를 가지는 activity인 경우라면 system popup이 떠있는 경우라도 hardware volume key에 의한 볼륨값을 해당 activity의 볼륨UI에 업데이트 해야하는 요구가 있을 수 있다.

이러한 경우에는 다음과 같은 방식을 사용한다.

1. BroadcastReceiver클래스 추가

class MediaVolumeChangeReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            boolean hasWindowFocus = %특정_Activity%.this.hasWindowFocus();

            // 현재 activity의 window에게 focus가 없는 경우의 처리
              //
            // 참고로, 키입력에 의해 이동하는 focus가 아니라 window들 중 f/g에 있는 window가
            // 어떤 것인지를 구분하기위한 focus이다.
            // 즉, 안드로이드 플랫폼에서 띄우는 전원종료와 같은 Dialog가 f/g에 있는 경우에 해당 값은
            // false이고, 해당 activity의 window가 f/g에 있다면 true이다.
            if (hasWindowFocus == false) {
                // TODO: 종료팝업이 떠 있는 경우에 volume UI 업데이트 처리
            }
        }
    }
2. MediaVolumeChangeReceiver mMediaVolChgReceiver;
3. BroadcastReceiver 등록
    getApplicationContext().registerReceiver(mMediaVolChgReceiver, new  IntentFilter("android.media.VOLUME_CHANGED_ACTION"));

4. BroadcastReceiver 등록 해제
    getApplicationContext().unregisterReceiver(mMediaVolChgReceiver);

Activity가 System popup이 떠 있는지 여부를 확인하는 방법

Sub title: How to know there's a showing system popup on an activity?

간단하다.

Activity 클래스의 public boolean hasWindowFocus()를 사용하면 된다.

참고로, 해당 플랫폼 코드는 아래와 같다.


    /**
     * Returns true if this activity's <em>main</em> window currently has window focus.
     * Note that this is not the same as the view itself having focus.
     *
     * @return True if this activity's main window currently has window focus.
     *
     * @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams)
     */
    public boolean hasWindowFocus() {
        Window w = getWindow();
        if (w != null) {
            View d = w.getDecorView();
            if (d != null) {
                return d.hasWindowFocus();
            }
        }
        return false;
    }


2012년 12월 9일 일요일

adb procrank command

Android에서 가장 정확한 memory usage infomation을 제공한다.

tnkim@tnkim-N53SV:~/git/drawingmemo$ adb shell procrank -h
Usage: procrank [ -W ] [ -v | -r | -p | -u | -h ]
    -v  Sort by VSS.
    -r  Sort by RSS.
    -p  Sort by PSS.
    -u  Sort by USS.
        (Default sort order is PSS.)
    -R  Reverse sort order (default is descending).
    -w  Display statistics for working set only.
    -W  Reset working set of all processes.
    -h  Display this help screen.

중요한 정보는 아래와 같다.
  • USS(Unique Set Size)
    • 프로세스만의 고유한 페이지 수. 즉, 공유되지 않은 프로세스의 private한 메모리 크기이다.
  • PSS(Proportional Set Size)
    • USS + (공유 페이지 / 공유하는 프로세스 수)
    • 즉, 프로세스 고유 메모리 사용량 + 하나의 프로세스가 차지하는 공유 메모리 비율
    • 예. 만약 A프로세스가 6MB 메모리를 사용하고 그 중 2MB가 그 프로세스의 고유 영역(USS)이라면, 나머지 4MB는 공유 메모리이다. 4MB의 공유메모리를 4개의 프로세스가 공유하고 있다면 PSS는 2MB + (4MB/4) = 3MB가 된다.
    • 프로세스별 PSS 수치는 adb shell dumpsys meminfo <processname> 을 통해서도 볼 수 있다.
### start of procrank command example ###
tnkim@tnkim-N53SV:~/git/drawingmemo$ adb shell procrank
  PID      Vss            Rss         Pss          Uss        cmdline
 1296   49828K   47396K   18075K   15048K  system_server
 1357   46140K   39876K   11308K    9244K  com.android.systemui
 2951   41388K   38260K    9937K    7892K  com.android.gallery3d
 4174   34972K   34880K    8019K    6400K  android.process.acore
 1186   36564K   36440K    6267K    3468K  zygote
 1525   32160K   32108K    5957K    4676K  com.android.phone
 1492   31620K   31532K    5460K    4248K  android.process.media
 4246   29156K   29060K    3758K    2536K  com.android.quicksearchbox
 4191   29152K   29080K    3600K    2476K  com.android.keychain
 4158   28684K   28616K    3442K    2372K  com.android.defcontainer
 1188    6896K    6896K    2756K    2400K  /system/bin/mediaserver
...
 4991     116K     116K      67K      64K  /sbin/adbd
 8279     124K     124K      63K      56K  /sbin/adbd
 8006     120K     120K      63K      56K  /sbin/adbd
                          ------   ------  ------
                         280030K  239196K  TOTAL

RAM: 868796K total, 275900K free, 27536K buffers, 194924K cached, 1136K shmem, 14140K slab
### end of procrank command example ###

* 참고 사이트