分類
Android

把安裝在手機的 apk 複製到本地端

0.開啟目前平台提供的最基本的Terminal tool
windows使用power shell(不要使用git bash),ubuntu使用bash,並移動到android sdk/platform-tools/
1.列出所有安裝的 package
(以下以Twitter app為範例,請確認已安裝Twitter)

adb shell pm list packages

Note:
在列出的 package 中尋找目標 apk 的 package,通常 package 和目標 apk 會有關連
若使用 adb 出現 command not found, 請指定 adb 的絕對路徑, (e.g., /android-sdk/platform-tools/adb), 或參考這篇將 adb 加到環境變數中。

./adb.exe shell pm list packages
output:
...
package:com.google.android.accessibility.soundamplifier
package:com.twitter.android
package:com.android.musicfx
...

 
2.藉由 package 列出目標 apk 的完整路徑

adb shell pm path example.package_name

Note:
example.package_name 就是在第1步列出的目標 apk package

./adb.exe shell pm path com.twitter.android
output:
package:/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/base.apk
package:/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/split_config.arm64_v8a.apk
package:/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/split_config.xxhdpi.apk
package:/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/split_config.zh.apk

 
3.複製目標到本地端

adb pull /data/app/example.package_name /local/path

Note:
/data/app/example.package_name 就是在第2步找到的完整路徑
local/path/ 為想放置目標 apk 的本地端(目前操作的機器)路徑,路徑之後可以設定取出的Apk的名稱,如D:\tempAPK\twitter_temp.apk。

./adb.exe pull /data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/base.apk D:\tempAPK\twitter_temp.apk
output:
/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/base.apk: 1 file pulled. 35.1 MB/s (17336308 bytes in 0.470s)

完成後在D:\tempAPK\twitter_temp.apk就是從裝置取出的Twitter apk

分類
Uncategorized

加入 Android SDK 路徑到環境變量 (ubuntu12.04 LTS)

目的 :

以後要使用 android adb 以及相關工具不必指定絕對路徑。

環境:

Ubuntu 12.04 LTS
zsh or bash
android-sdk_r20-linux

步驟:

1.將 android sdk path 加入環境變數

使用 bash 的情況
使用編輯工具修改 ~/.bashrc, 加入以下指令

export ANDROID_HOME=$HOME/android_sdk_path
export PATH=$PATH:$ANDROID_HOME/tools:$PATH:ANDROID_HOME/platform-tools

第1行的 /android_sdk_path 為本機端 android sdk 路徑


使用 zsh 的情況
使用編輯工具修改 ~/.zshrc, 加入以下指令

export PATH=$PATH:$ANDROID_SDK_HOME/tools:$ANDROID_SDK_HOME/platform-tools
export ANDROID_SDK_HOME=/android_sdk_path

第2行 /android_sdk_path 為本機端 android sdk 路徑

2.更新環境變數

source ~/.bashrc
or
source ~/.zshrc

 

3.測試結果

開啟一個新的 console 並輸入 adb 會出現以下提示訊息

$ adb
Android Debug Bridge version 1.0.32
Revision 09a0d98bebce-android
 -a                            - directs adb to listen on all interfaces for a connection
 -d                            - directs command to the only connected USB device
                                 returns an error if more than one USB device is present.
 -e                            - directs command to the only running emulator.
                                 returns an error if more than one emulator is running.
 -s <specific device>          - directs command to the device or emulator with the given
                                 serial number or qualifier. Overrides ANDROID_SERIAL
                                 environment variable.
 -p <product name or path>     - simple product name like 'sooner', or
                                 a relative/absolute path to a product
                                 out directory like 'out/target/product/sooner'.
                                 If -p is not specified, the ANDROID_PRODUCT_OUT
                                 environment variable is used, which must
                                 be an absolute path.
 ...

 

分類
Uncategorized

D-link DWA-131 install on Ubuntu 12.04 LTS

目的:

讓 DWA-131 可以在 ubuntu 12.04 LTS 上正常使用。
DWA-131 全名為 DWA-131 Wireless N NANO USB 無線網路卡。雖然官網在系統支援的部份沒有提到 Linux。

但有提供 linux driver 可以下載。來試著安裝看看。我的環境為 ubuntu 12.04 LTS。

步驟:

首先下載並解壓縮官網提供的 driver 會產生rtl8188C_8192C_usb_linux_v3.4.4_4749.20120730 資料夾,進到資料夾中沒有 readme,只好先 make 看看。
make 過程會出現一點小錯,大致是一些編譯參數不正確的問題,最後編譯過了,但使用 ifconfig 等等指令還是找不到無線網卡,還好找到這篇(http://askubuntu.com/questions/693500/dlink-dwa-131-wireless-adapter-not-working-on-ubuntu-15-10)。
就照著 Try this – https://github.com/Mange/rtl8192eu-linux-driver 的解法,成功編譯並啟動無線網卡。
 

分類
Android

Robotium 使用介紹

Robotium 為專門測試 android ui 的 framework,詳細介紹參考官網
本篇介紹如何導入 Robotium,以及建立測試 Activity 的 Robotium 模板,最後是 Robotium 常用函式介紹。


如何導入 Robotium

  1. Robotium 的使用環境和 android test project 非常類似,因此先建立 android test project。
    建立 android test project 詳細步驟請參考這篇
  2. 在第1步建立的 android test project 根目錄中建立 libs 資料夾,並將 Robotium 提供的 jar 檔放入 libs ,android 會自動幫你做好其他事。

建立測試 Activity 的 Robotium 模板

完成導入 Robotium 之後,你應該有個 test case,再將以下的 mSolo 加入。
Solo 為 Robotium 提供的類別,大部分測試功能都由它開始。

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity>
{
    private Solo mSolo;
    public MainActivityTest(String name) {
        super(MainActivity.class);
        setName(name);
    }
    public MainActivityTest()
    {
        this(MainActivityTest.class.getSimpleName());
    }
    protected void tearDown() throws Exception
    {
        mSolo.finishOpenedActivities();
        super.tearDown();
    }
    protected void setUp() throws Exception
    {
        super.setUp();
        mSolo = new Solo(getInstrumentation(), getActivity());
    }
    public void testPreconditions()
    {
       assertNotNull(mSolo);
    }
}

第24行初始化 mSolo
第17行銷毀 mSolo
以上就是測試 activity 的 Robotium 模板


Robotium 常用函式介紹

使用 Robotium 來寫測試主要分為3個步驟:

  1. 取得 UI Component
  2. 操作 UI Component
  3. 使用斷言(Assert)判斷操作結果是否符合預期

接下來依序介紹在 Robotium 中各個步驟如何進行

取得 UI Component

在 Robotium 主要有2種取得 UI Component 的方式,第1種為根據 UI Component 的 ID 來取得,適合用於 UI Component 具有唯一的 ID 情況。

android.view.View getView(int id)

Returns a View matching the specified resource id.
android.view.View getView(String id)

Returns a View matching the specified resource id.

Example

...
LinearLayout mainView = (LinearLayout) mSolo.getView(R.id.main_view);//R.id.main_view 為 resource id

UI Component 的內容值可以當作識別的方式,但這種方式只支援 Button, EditText, TextView, 適合用於 UI Component 具有內容值的情況。

android.widget.Button getButton(String text)

Returns a Button displaying the specified text.
android.widget.EditText getEditText(String text)

Returns an EditText displaying the specified text.
android.widget.TextView getText(String text)

Returns a TextView displaying the specified text.

Example

...
Button exitButton = mSolo.getButton("Exit");//Exit 為顯示在 button 上的內容

UI Component 的 Tag 可以當作識別的方式,適合用於 UI Component 具有 Tag 的情況下。

android.view.View getView(Object tag)

Returns a View matching the specified tag.

第2種為根據 UI Component 的類型進行過濾再根據索引來取得,這種情況適用於 UI Component 沒有可以識別的 ID 情況。以下第1個方法為回傳所有類型的 views, 第2個回傳指定類型的 views, 第3個回傳指定類型並指定 parent 的 views

ArrayList<android.view.View> getCurrentViews()

Returns an ArrayList of the Views currently displayed in the focused Activity or Dialog.
<T extends android.view.View>
ArrayList<T>
getCurrentViews(Class<T> classToFilterBy)

Returns an ArrayList of Views matching the specified class located in the focused Activity or Dialog.
<T extends android.view.View>
ArrayList<T>
getCurrentViews(Class<T> classToFilterBy, android.view.View parent)

Returns an ArrayList of Views matching the specified class located under the specified parent.

使用上述的方法取得 view 的群集之後,再使用以下方法取得個別的 view。
需要注意的是 index 該 index 為子視圖的順序(index 從零開始由左到右或上到下遞增)

android.widget.Button getButton(int index)

Returns a Button matching the specified index.
android.widget.EditText getEditText(int index)

Returns an EditText matching the specified index.
android.widget.ImageView getImage(int index)

Returns an ImageView matching the specified index.
android.widget.ImageButton getImageButton(int index)

Returns an ImageButton matching the specified index.
android.widget.TextView getText(int index)

Returns a TextView matching the specified index.
<T extends android.view.View>
T
getView(Class<T> viewClass, int index)

Returns a View matching the specified class and index.

Example

...
LinearLayout mainView = (LinearLayout) mSolo.getView(R.id.main_view);
List<Button> buttons = mSolo.getCurrentViews(Button.class, mainView);
for (int i=0; i<buttons.size(); ++i) {
    Button button = mSolo.getButton(i);
}
//以上的 source code 即是取得 mainView 中的所有 button

操作 UI Component

Robotium 提供的操作共有點擊,長按,輸入文字,拖曳,滾動,搜尋,等待。
點擊&長按
點擊和長按的方法大致上各為clickOnXXX, clickLongOnXXX,支援的種類有 View, Button, EditText, ImageView, ImageButton, CheckBox, MenuItem, RadioButton, ToggleButton, Screen座標等等 。
詳細參考官網
Example :

...
mSolo.clickOnButton("Exit");//點擊 Exit button

輸入和清除文字
各為 typeText 以及 enterText,都是對 EditText 輸入文字,2者不同為 type 會一個一個的輸入,而 enter 為直接全部輸入 ,清除則是只對 EditText 清除 (cleanEditText)。
詳細參考官網
Example :

...
EditText numberEditText = mSolo.getEditText("number");//取得內容為 number 的 EditText
mSolo.typeText(numberEditText, "123");//在 EditText 填入 123

拖曳

void drag(float fromX, float toX, float fromY, float toY, int stepCount)

Simulate touching the specified location and dragging it to a new location.
...
mSolo.drag(0.0f, 50.0f, 0.0f, 50.0f, 100);

需要注意的是原點從左上角開始算起。
滾動
滾動為 ScrollXXX,基本的有 ScrollDown, ScrollUp 往上下滾動一段。
ScrollToBottom, ScrollToTop 往頂端底端滾動。
ScrollToSide 滾動至左或右。進階可以指定滾動的距離。
支援的種類有 view, ListView, RecyclerView,
詳細參考官網

mSolo.scrollDown();

搜尋
searchXXX,搜尋的目的為確認 UI Compenent 是否存在於 Screen 上,有的話才進行下一步操作。支援 Button, EditText, TextView, ToggleButton, 詳細參考官網
Example :

...
if(!mSolo.searchButton("Exit")){//尋找 Exit button 是否在螢幕上
    fail("Exit button should on screen");
}

等待
waitForXXX為等待系列,支援 activity, dialog open dialog close, text, view。
需要注意的是 waitFor 函式會在其內預設等待20秒,在20秒之內若等待條件成立的話回傳 true,反之回傳 false,也有提供自定義時間 e.g. mSolo.waitForLogMessage(“”,5000)。
比較特殊等待條件有 waitForLogMessage,這裡的 LogMessage 即為使用 Log 列印出來的內容
e.g. Log.d(TAG,”log message should print”);
就可使用 mSolo.waitForLogMessage(“log message should print”);
另一個是 waitForCondition , 可以自定義複雜條件以滿足特別的需求。
詳細參考官網
另外還有其他操作 e.g. 休息(sleep),擷圖(takeScreenShot),模擬按下實體按鍵, 檢查 UI 狀態(isXXX)。


使用斷言(Assert)判斷操作結果是否符合預期

因為 android test project 預設為使用 junit 來斷言,對於絕大部分的斷言情況來說已經足夠。Robotium 提供的斷言不多,只有4種(assertCurrentAcitvity, assertMemoryNotLow)。
Example :

...
assertTrue(mSolo.searchButton("Exit"));//斷言 Exit button 需要在螢幕上

 
 
 
 

分類
Android

TouchUtils.clickView 對 button 無效

描述:

button 是一般的 android 預設的 button,xml 定義如下

        <Button
            android:id="@+id/showtime_btn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:text="Show Time" />

被測程式 source code 使用如下

public class MainActivity extends Activity implements OnClickListener
{
    private Button mShowTimeButton;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initUIComponents();
    }
    private void initUIComponents()
    {
       mShowTimeButton = (Button) findViewById(R.id.showtime_btn);
       mShowTimeButton.setOnClickListener(this);
    }
    @Override
    public void onClick(View view)
    {
       int uiId = view.getId();
       switch(uiId){
           case R.id.showtime_btn:
               System.out.println("click show time button");
               break;
       }
    }
}

在直接執行被測程式的情況下, mShowTimeButton 可以點擊且點擊會出現click show time button。
測試程式如下

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity>
{
    private static final String DEBUG = MainActivityTest.class.getSimpleName();
    private MainActivity mMainActivity;
    private Button mShowTimeButton;
    public MainActivityTest() {
        super("com.example.targettestproject", MainActivity.class);
    }
    protected void setUp() throws Exception
    {
        super.setUp();
        setActivityInitialTouchMode(true);
        mMainActivity = getActivity();
        initUIComponents();
    }
    private void initUIComponents()
    {
       mShowTimeButton = (Button) mMainActivity.findViewById(R.id.showtime_btn);
    }
    protected void tearDown() throws Exception
    {
        super.tearDown();
    }
    public void testPrecondition()
    {
        assertNotNull(mMainActivity);
        assertNotNull(mShowTimeButton);
    }
    @UiThreadTest
    public void testShowTimeByClickButton()
    {
        TouchUtils.clickView(this, mShowTimeButton);
    }
}

第15行經過測試有沒有加入 setActivityInitialTouchMode 沒有影響。
第37行經過測試有沒有加入 @UiThreadTest 沒有影響。
第40行沒有效果。
經過 google 相關搜尋沒有找到類似的問題及解決方案。
 

修正:

使用 @UiThreadTest 以及 Button.performClick() 來取代 TouchUtils.clickView,如下

    ...
    @UiThreadTest
    public void testShowTimeByClickButton()
    {
        mShowTimeButton.performClick();
    }

 

分類
Android

使用 TDD 加入 android 測試案例

上一篇 建立測試案例以及測試 android activity 的模板 建立基本模板以及測試案例之後。本篇開始介紹如何使用 TDD 的方式加入測試。
TDD(測試驅動開發)起源於 XP(極限編程)主張先寫測試再寫代碼的循環,循環的步驟如下:

  1. 用代碼寫需求,必須符合單元測試。(寫測試代碼)
  2. 測試失敗。(因為還未產生產品代碼)。
  3. 編寫代碼,實現需求。(實現產品代碼)
  4. 測試通過。
  5. 重構。

TDD需要為每段產品代碼編寫測試案例,並讓測試案例優先,測試案例定義產品代碼需要做什麼,並保證產品代碼確實符合規定。
首先來建立需求清單。

  1. 被測程式可以取得時間並以數位方式顯示在螢幕上。
  2. 使用者藉由點擊按鈕來顯示時間。
  3. 使用者藉由點擊按鈕來離開程式,離開前必須出現對話框以確定是否真正離開。

被測程式以及測試程式已經在前幾篇建立完成,接下來開始 TDD。
藉由需求清單我們可以考慮螢幕需要兩個按鈕用來啟動顯示時間以及啟動對話框,另外還需要一個 TextView 用來顯示時間。
因此我們先寫測試來建立 UI component,在測試程式的 MainActivityTest 加入以下代碼

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity>
{
    private MainActivity mMainActivity;
    private Button mShowTimeButton;
    private Button mExitButton;
    private TextView mTimeView;
    public MainActivityTest() {
        super("com.example.targettestproject", MainActivity.class);
    }
    protected void setUp() throws Exception
    {
        super.setUp();
        mMainActivity = getActivity();
        initUIComponents();
    }
    private void initUIComponents()
    {
       mShowTimeButton = mMainActivity.findViewById(R.id.showtime_btn);
       mExitButton = mMainActivity.findViewById(R.id.exit_btn);
       mTimeView = mMainActivity.findViewById(R.id.time_view);
    }
    protected void tearDown() throws Exception
    {
        super.tearDown();
    }
    public void testPrecondition()
    {
        assertNotNull(mMainActivity);
        assertNotNull(mExitButton);
        assertNotNull(mShowTimeButton);
        assertNotNull(mTimeView);
    }
}

第5~7行即為被測程式中使用到的 UI component,保存在欄位中方便測試。
第17行在 setUp 呼叫初始化 UI component 的動作。
第35~37行斷言這些 UI component 不可為 null。
到目前為止完成 TDD 的第1個步驟寫測試,但因為被測程式中還不存在這些 UI component,因此現在測試程式還無法執行(測試失敗)。準備進行第3步(編寫代碼,實現需求)我們到測試程式中建立這些 UI component
在 TargetTestProject/src/com/example/targettestproject/MainActivity 加入以下 source code

public class MainActivity extends Activity
{
    private Button mShowTimeButton;
    private Button mExitButton;
    private TextView mTimeView;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initUIComponents();
    }
    private void initUIComponents()
    {
       mShowTimeButton = (Button) findViewById(R.id.showtime_btn);
       mExitButton = (Button) findViewById(R.id.exit_btn);
       mTimeView = (TextView) findViewById(R.id.time_view);
    }
}

並開啟 TargetTestProject/res/layout/main.xml 加入以下 source code

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/time_view"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_gravity="center"
        android:layout_weight="0.6"
        android:gravity="center"
        android:textSize="30sp" />
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="0.4"
        android:orientation="horizontal" >
        <Button
            android:id="@+id/showtime_btn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:text="Show Time" />
        <Button
            android:id="@+id/exit_btn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:text="Exit" />
    </LinearLayout>
</LinearLayout>

完成 TDD 第3步編寫代碼實現需求。接著開始 TDD 第4步進行測試。輸入以下指令以執行測試

ant uninstall clean debug install test

輸出為

test:
     [echo] Running tests ...
     [exec]
     [exec] com.example.targettestproject.MainActivityTest:.
     [exec] Test results for InstrumentationTestRunner=.
     [exec] Time: 0.629
     [exec]
     [exec] OK (1 test)
     [exec]
     [exec]
BUILD SUCCESSFUL
Total time: 27 seconds

通過測試!!!
因為加入的 source code 相當簡單,並沒有發現其他程式碼怪味(Code Smell)。
第5步重構可以先忽略。上述過程為完成1次 TDD 循環的過程。
 
接著把剩下的需求轉換成測試案例,從第1項需求開始
“被測程式可以取得時間並以數位方式顯示在螢幕上。”
把 “取得時間” 轉換為 source code,在測試程式實作該測試案例。
MainActivityTest 加入以下 source code  (TDD 第1步和第2步)

    ...
    public void testGetTime()
    {
        String except = mMainActivity.getTime();
        assertNotNull(except);
    }

接著在 MainActivity 加入相對應的實作(TDD第3步)

    ...
    public String getTime()
    {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd kk:mm:ss", Locale.TAIWAN);
        return dateFormat.format(new Date());
    }

進行測試,輸入

ant uninstall clean debug install test

輸出如下

test:
[echo] Running tests ...
[exec]
[exec] com.example.targettestproject.MainActivityTest:..
[exec] Test results for InstrumentationTestRunner=..
[exec] Time: 1.257
[exec]
[exec] OK (2 tests)
[exec]
[exec]
BUILD SUCCESSFUL
Total time: 29 seconds

通過測試!!!(TDD第4步)
針對第1項需求的後半段 “以數位方式顯示在螢幕上” 代表 mTimeView 欄位必須能設定數值且數值必須正確才行。
TDD 第1步,加入測試案例。MainActivityTest 加入以下代碼

    ...
    public void testSetTimeToTimeView()
    {
        String time = mMainActivity.getTime();
        mTimeView.setText(time);
        assertEquals(time, mTimeView.getText());
    }

這一次不需要實作相對應的實作,因為 mTimeView.setText() 函式已經存在於android 本身。
執行測試,輸入

ant uninstall clean debug install test

測試失敗!!!輸出訊息如下

test:
     [echo] Running tests ...
     [exec]
     [exec] com.example.targettestproject.MainActivityTest:..
     [exec] Error in testSetTimeToTimeView:
     [exec] android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
     [exec]     at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4925)
     [exec]     at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:950)
     [exec]     at android.view.View.requestLayout(View.java:15461)
     [exec]     at android.view.View.requestLayout(View.java:15461)
     [exec]     at android.view.View.requestLayout(View.java:15461)
     [exec]     at android.view.View.requestLayout(View.java:15461)
     [exec]     at android.view.View.requestLayout(View.java:15461)
     [exec]     at android.widget.TextView.checkForRelayout(TextView.java:6645)
     [exec]     at android.widget.TextView.setText(TextView.java:3733)
     [exec]     at android.widget.TextView.setText(TextView.java:3591)
     [exec]     at android.widget.TextView.setText(TextView.java:3566)
     [exec]     at com.example.targettestproject.MainActivityTest.testSetTimeToTimeView(MainActivityTest.java:67)
     [exec]     at java.lang.reflect.Method.invokeNative(Native Method)
     [exec]     at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
     [exec]     at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:199)
     [exec]     at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:192)
     [exec]     at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:192)
     [exec]     at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:177)
     [exec]     at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)
     [exec]     at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1619)
     [exec]
     [exec] Test results for InstrumentationTestRunner=...E
     [exec] Time: 1.909
     [exec]
     [exec] FAILURES!!!
     [exec] Tests run: 3,  Failures: 0,  Errors: 1
     [exec]
     [exec]
BUILD SUCCESSFUL
Total time: 31 seconds

第6行說明失敗原因,只能在 UI thread 改變 UI component 的值才行,在其他 thread 改變都會失敗。
有不少方法可以修正該錯誤,我們採取最間單的方式。在該測試案例上加上 @UiThreadTest,該註解說明其內的動作全都會執行在 UI thread 上。加上該註解

    @UiThreadTest
    public void testSetTimeToTimeView()
    {
        String time = mMainActivity.getTime();
        mTimeView.setText(time);
        assertEquals(time, mTimeView.getText());
    }

再次執行測試,輸入

ant uninstall clean debug install test

測試結果輸出

test:
[echo] Running tests ...
[exec]
[exec] com.example.targettestproject.MainActivityTest:...
[exec] Test results for InstrumentationTestRunner=...
[exec] Time: 2.156
[exec]
[exec] OK (3 tests)
[exec]
[exec]
BUILD SUCCESSFUL
Total time: 30 seconds

測試成功!!!
接著 TDD 第2項需求
“使用者藉由點擊按鈕來顯示時間”
在MainActivityTest加入以下測試案例

    ...
    public void testShowTimeByClickButton()
    {
        TouchUtils.clickView(this, mShowTimeButton);
        String exceptTime = mMainActivity.getTime();
        assertEquals(exceptTime, mTimeView.getText().toString());
    }

這次的測試案例不會出現失敗,因為需要的函式實作在之前的步驟已經完成。
進行測試。

ant uninstall clean debug install test

測試失敗!!!,輸出如下

test:
     [echo] Running tests ...
     [exec]
     [exec] com.example.targettestproject.MainActivityTest:...
     [exec] Failure in testShowTimeByClickButton:
     [exec] junit.framework.ComparisonFailure: expected:<[2017/02/20 10:06:30]> but was:<[]>
     [exec]     at com.example.targettestproject.MainActivityTest.testShowTimeByClickButton(MainActivityTest.java:80)
     [exec]     at java.lang.reflect.Method.invokeNative(Native Method)
     [exec]     at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
     [exec]     at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:199)
     [exec]     at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:192)
     [exec]     at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:192)
     [exec]     at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:177)
     [exec]     at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)
     [exec]     at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1619)
     [exec]
     [exec] Test results for InstrumentationTestRunner=....F
     [exec] Time: 4.306
     [exec]
     [exec] FAILURES!!!
     [exec] Tests run: 4,  Failures: 1,  Errors: 0
     [exec]
     [exec]
BUILD SUCCESSFUL
Total time: 44 seconds

從第6行可以看到預期的值為 2017/02/20 10:06:30 ,但是實際的值為空白。
雖然相關的函式實作已經完成,但將其連結的邏輯還未完成。
在目前的動作邏輯中,點擊按鈕並不會顯示時間。
因此我們回到被測專案(MainActivity)中加入以下代碼

public class MainActivity extends Activity implements OnClickListener
{
    private Button mShowTimeButton;
    private Button mExitButton;
    private TextView mTimeView;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initUIComponents();
    }
    private void initUIComponents()
    {
       mShowTimeButton = (Button) findViewById(R.id.showtime_btn);
       mShowTimeButton.setOnClickListener(this);
       mExitButton = (Button) findViewById(R.id.exit_btn);
       mTimeView = (TextView) findViewById(R.id.time_view);
    }
    public String getTime()
    {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd kk:mm:ss", Locale.TAIWAN);
        return dateFormat.format(new Date());
    }
    @Override
    public void onClick(View view)
    {
       int uiId = view.getId();
       switch(uiId){
           case R.id.showtime_btn:
               mTimeView.setText(getTime());
           break;
       }
    }
}

第1行讓Activity 實作 OnClickListener 介面
第19行讓 mShowTimeButton 加入onClickListener 監聽器
第31~39行實作 onClick 函式內容即為將點擊按鈕連結到顯示時間
將動作的連結邏輯實作完成後,再來執行測試。

ant uninstall clean debug install test

測試通過!!!,輸出如下

test:
     [echo] Running tests ...
     [exec]
     [exec] com.example.targettestproject.MainActivityTest:....
     [exec] Test results for InstrumentationTestRunner=....
     [exec] Time: 1.931
     [exec]
     [exec] OK (4 tests)
     [exec]
     [exec]
BUILD SUCCESSFUL
Total time: 29 seconds

最後我們增加最後一項需求的測試案例。
“使用者藉由點擊按鈕出現對話框以確定是否真正離開程式。”
在這裡我們加入 Robotium 來更方便的寫測試,Robotium 是一個專門測試 android ui 的 framework,詳細介紹請到官網
導入的步驟其實就是將 Robotium 的 jar 檔加到測試專案。
首先在測試專案的根目錄建立 libs 資料夾,該資料夾中的 jar 檔都會被自動的連結到該專案中。到官網下載jar檔,目前(2017/02/20)最新的版本為 Robotium-5.6.3.jar
接著在 MainActivityTest 加入以下代碼

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity>
{
    private static final String DEBUG = MainActivityTest.class.getSimpleName();
    private Solo mSolo;
    protected void setUp() throws Exception
    {
        super.setUp();
        mSolo = new Solo(getInstrumentation(), getActivity());
        mMainActivity = getActivity();
        ...
    }
    protected void tearDown() throws Exception
    {
        mSolo.finishOpenedActivities();
        super.tearDown();
    }
    public void testShowExitDialogByClickExitButton()
    {
        mSolo.clickOnButton("Exit");
        assertTrue(mSolo.waitForDialogToOpen());
    }
    public void testNoButtonActionOnExitDialog()
    {
        mSolo.clickOnButton("Exit");
        boolean isExitDialogOpen = mSolo.waitForDialogToOpen();
        if (isExitDialogOpen) {
            mSolo.clickOnButton("no");
            assertTrue(mSolo.waitForDialogToClose());
        }
    }
    public void testYesButtonActionOnExitDialog()
    {
        mSolo.clickOnButton("Exit");
        boolean isExitDialogOpen = mSolo.waitForDialogToOpen();
        if (isExitDialogOpen) {
            mSolo.clickOnButton("yes");
            mSolo.sleep(5000);
            assertFalse(mSolo.searchButton("Show Time"));
        }
    }
}

第5行的 Solo 為 Robotium 最常使用的類別,我們需要一個欄位(mSolo)來保存它。
第10行測試前初始化 mSolo。
第17行測試後銷毀 mSolo。
第23行就是使用 Robotium 的方便之處,Robotium 會去目前頁面中搜尋符合 clickOnButton 的參數值(Exit),找到 button 的話就會去點擊它。
第24行 waitForDialogToOpen 代表會在預設的時間(20sec)內等待 dialog 顯示,如果20秒之內有 dialog 顯示就會回傳 true,反之則回傳 false。
第27行測試當點擊 exit 對話框的 no button 動作是否如預期。我們預期點擊 no button 之後,exit 對話框會關閉,回到主畫面。
第37行測試當點擊 exit 對話框上的 yes button 的動作是否如預期。我們預期點擊 yes button 之後,exit 對話框會關閉回到主畫面,MainActivity 會 finish,因此等待5秒再去尋找 Show Time 按鈕是不存在的。
寫完以上的測試代碼之後,開始寫產品代碼。
在 MainActivity 加入以下代碼

public class MainActivity extends Activity implements OnClickListener
{
    private Button mShowTimeButton;
    private Button mExitButton;
    private TextView mTimeView;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initUIComponents();
    }
    private void initUIComponents()
    {
        mShowTimeButton = (Button) findViewById(R.id.showtime_btn);
        mShowTimeButton.setOnClickListener(this);
        mExitButton = (Button) findViewById(R.id.exit_btn);
        mExitButton.setOnClickListener(this);
        mTimeView = (TextView) findViewById(R.id.time_view);
    }
    public String getTime()
    {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd kk:mm:ss", Locale.TAIWAN);
        return dateFormat.format(new Date());
    }
    @Override
    public void onClick(View view)
    {
        int uiId = view.getId();
        switch (uiId) {
            case R.id.showtime_btn:
                mTimeView.setText(getTime());
                break;
            case R.id.exit_btn:
                showExitDialog();
                break;
        }
    }
    private void showExitDialog()
    {
        final AlertDialog exitDialog = new AlertDialog.Builder(this).create();
        exitDialog.setMessage("Exit app?");
        exitDialog.setButton(DialogInterface.BUTTON_POSITIVE, "yes", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
               finish();
            }
        });
        exitDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "no", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
              exitDialog.dismiss();
            }
        });
        exitDialog.show();
    }
}

第21行讓 mExitButton 設定 listener,對象為 MainActivity。
第39~41行建立點擊 mExitButton 動作,當點擊 mExitButton 之後會呼叫showExitDialog()
第45行即為 showExitDialog 函式本身,函式會建立含有2個 button (yes, no)的 dialog,並對這2個 button 建立相對應的動作。
完成產品代碼之後來執行測試,輸入

ant uninstall clean debug install test

測試通過!!!,輸出為

test:
     [echo] Running tests ...
     [exec]
     [exec] com.example.targettestproject.MainActivityTest:.......
     [exec] Test results for InstrumentationTestRunner=.......
     [exec] Time: 30.024
     [exec]
     [exec] OK (7 tests)
     [exec]
     [exec]
BUILD SUCCESSFUL
Total time: 1 minute 0 seconds

以上即為使用 TDD 來建立 android test 的過程,比較需要注意的是如果需求有對 UI Component 設定位置標準,如對齊,置中等等,就必須加入這些測試案例。
另一點為可以強化對斷言(assert)的條件,如以下 testGetTime 的測試案例

    public void testGetTime()
    {
        String except = mMainActivity.getTime();
        assertNotNull(except);
    }

我們僅對 getTime 測試不可為 null,事實上還可以測試它的格式是否符合標準(yyyy/MM/dd kk:mm:ss),測試是否位於合理範圍值等等。
 
 
 
 
 

分類
Android

建立測試案例以及測試 android activity 的模板

在上一篇 Create android test project by command line and eclipse 中我們建立被測專案(TargetTestProject)以及測試專案(TestProject),並在測試專案中加入一個測試Activity的 Test Case(MainActivityTest)。
但 MainActivityTest 不算是一個真正的 Test Case,因為裡面沒有任何測試案例。
因此本篇紀錄如何加入測試案例以及測試Activity的基本模板。
加入測試案例相當簡單,因為 android test framework 使用 Junit3,而 Junit3 會將所有公開,無回傳值並以 test 開頭的方法都認為是測試案例,e.g. public void testXXX()
因此在MainActivityTest 加入以下代碼,即為一個測試案例。

    public void testPrecondition()
    {
        fail("test Precondition");
    }

testPrecondition 會是基本模板中的一部分,裡面主要是測試所有前置條件是否通過。
而 fail(“test Precondition”),代表測試程式執行到這一定會失敗並印出提示內容。
接著執行測試專案,會看到以下輸出

test:
     [echo] Running tests ...
     [exec]
     [exec] com.example.targettestproject.MainActivityTest:
     [exec] Failure in testPrecondition:
     [exec] junit.framework.AssertionFailedError: test precondition
     [exec]     at com.example.targettestproject.MainActivityTest.testPrecondition(MainActivityTest.java:36)
     [exec]     at java.lang.reflect.Method.invokeNative(Native Method)
     [exec]     at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
     [exec]     at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:199)
     [exec]     at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:192)
     [exec]     at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:192)
     [exec]     at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:177)
     [exec]     at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)
     [exec]     at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1619)
     [exec]
     [exec] Test results for InstrumentationTestRunner=.F
     [exec] Time: 0.493
     [exec]
     [exec] FAILURES!!!
     [exec] Tests run: 1,  Failures: 1,  Errors: 0
     [exec]
     [exec]
BUILD SUCCESSFUL
Total time: 19 seconds

輸出的內容有點多,但重要的只有第6行 test precondition,代表測試程式目前如我們所預期的執行動作。
接著加入測試Activity的基本模板。

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
    private MainActivity mMainActivity;
    public MainActivityTest() {
        super("com.example.targettestproject", MainActivity.class);
    }
    protected void setUp() throws Exception
    {
        super.setUp();
        mMainActivity = getActivity();
    }
    protected void tearDown() throws Exception
    {
        super.tearDown();
    }
    public void testPrecondition()
    {
       assertNotNull(mMainActivity);
    }
}

第3行的 mMainActivity 即為被測專案的 MainActivity,我們需要一個欄位來保存它,因為之後需要藉由它取得其他元件。
第9行的 setUp 會在 ” 每個 ” 測試案例執行前呼叫,主要做初始化的動作,包含建立物件等等,因此可以看到mMainActivity 也在這裡初始化。
第15行的 tearDown 相對於 setUp,會在每個測試案例結束後執行,若測試案例有需要釋放資源,銷毀物件如 Service, ContentProvider 等等,請在這裡將他們指定為Null。
第20行測試前置條件 mMainActivity 不可為 Null。
以上即為測試Activity的基本模板。
若在各個函式之間加入log觀察執行順序,如下

    private static final String DEBUG_TAG = MainActivityTest.class.getSimpleName();
    protected void setUp() throws Exception
    {
        super.setUp();
        mMainActivity = getActivity();
        Log.d(DEBUG_TAG, "in setUp");
    }
    protected void tearDown() throws Exception
    {
        super.tearDown();
        Log.d(DEBUG_TAG, "in tearDown");
    }
    public void testPrecondition()
    {
        assertNotNull(mMainActivity);
        Log.d(DEBUG_TAG, "in testPrecondition");
    }

輸出如下

D/MainActivityTest( 9367): in setUp
D/MainActivityTest( 9367): in testPrecondition
D/MainActivityTest( 9367): in tearDown

可以看到 setUp 會在 testPrecondition 之前呼叫,而 tearDown 會在 testPrecondition 之後呼叫。
下一篇會在被測程式加入 UI 功能並在測試程式加入相對應的測試案例。
 
 
 

分類
Android

Create android test project by command line and eclipse

使用 Eclipse 建立 android test project

Step 1. 開啟 Eclipse -> File -> new -> other -> 輸入 android test project -> next
Step 2.輸入 測試專案名稱

Step 2.a 選擇 finish -> Step 5.

Step 2.b 選擇 next -> Step 3.

Step 3.選擇被測專案

Step 3.a 選擇 next -> Step 4.

Step 3.b 選擇 finish -> Step 5.

Step 4.選擇 target sdk version
Step 5.建立測試專案完成!

使用 Command line 建立 android test project

Step 1. 開啟終端機並使用 android-sdk/tools/android 建立測試專案,建立測試專案的指令如下

android create test-project -p 測試專案路徑 -m 被測專案路徑
-p 代表測試專案路徑
-m 代表被測專案路徑

Note :

  1. 這2個參數都是必須的,因此使用 command line 建立測試專案必須存在被測專案才行。
  2. 被測專案路徑會由被測專案路徑中是否存在 AndroidManifest.xml 來判斷,若被測專案不存在 AndroidManifest.xml 會出現以下錯誤訊息
    Error: No AndroidManifest.xml file found in the main project directory:
  3. 若直接輸入 android 出現 command not found 的錯誤,請參考這篇(將相關指令加入環境變數

以下為範例:

首先建立被測專案 TargetTestProject
可以使用 eclipse 也可以使用 command line,以下使用指令
Step 1. 建立TargetTestProject資料夾,並移動該資料夾中

mkdir TargetTestProject
cd TargetTestProject

Step 2.建立 android project

android create project -p ./ -n TargetTestProject -k com.example.targettestproject -a MainActivity -t android-15
-p path to project directory
-n name of application
-k name of package
-a name of helloworld activity
-t android version

完成後會自動產生一個 MainActivity.java 位置在
TargetTestProject/src/com/example/targettestproject/MainActivity 可以啟動看看是否正常,在已連接裝置或是已啟動 AVD 的狀況下輸入

adb shell am start -n com.example.targettestproject/com.example.targettestproject.MainActivity

應該就可看到該app已啟動完成顯示在裝置上。
再建立測試專案 TestProject

cd ../
mkdir TestProject
cd TestProject
android create test-project -p ./ -m ../TargetTestProject

以上指令除了建立測試專案之外還會幫我們建立一個 test case 為 MainActivityTest.java
該名稱會對應於被測專案的Activity字尾加上Test
位置在 TargetTestProject/src/com/example/targettestproject/MainActivityTest.java
內容如下

package com.example.targettestproject;
import android.test.ActivityInstrumentationTestCase2;
/**
 * This is a simple framework for a test of an Application.  See
 * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
 * how to write and extend Application tests.
 * <p/>
 * To run this test, you can type:
 * adb shell am instrument -w \
 * -e class com.example.targettestproject.MainActivityTest \
 * com.example.targettestproject.tests/android.test.InstrumentationTestRunner
 */
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
    public MainActivityTest() {
        super("com.example.targettestproject", MainActivity.class);
    }
}

現在就可以試著啟動測試程式看看。
你可以使用註釋中提到的方式啟動,但是缺點是修改 source code 之後並不會重新編譯以及安裝,如下

 * adb shell am instrument -w \
 * -e class com.example.targettestproject.MainActivityTest \
 * com.example.targettestproject.tests/android.test.InstrumentationTestRunner
 */

不如使用 ant 來的方便,指令比較短也會自動完成需要的工作。

ant uninstall clean debug install test

啟動並完成測試之後,可以看到以下輸出

...
test:
     [echo] Running tests ...
     [exec]
     [exec] Test results for InstrumentationTestRunner=
     [exec] Time: 0.0
     [exec]
     [exec] OK (0 tests)
     [exec]
     [exec]
BUILD SUCCESSFUL
Total time: 16 seconds

成功執行測試,沒有任何測試案例。
下一篇 建立測試案例以及測試 android activity 的模板
 
 

分類
Android Uncategorized

在 Eclipse 使用 Android Manager 更新 android sdk 25之後無法使用 adb 問題

開啟 eclipse 啟動 android manager 並更新到 android adk 25.0.2 之後,使用 adb 會出現以下錯誤訊息

platform-tools ./adb devices
zsh: 可執行檔格式錯誤: ./adb

root cause:unknow
workaround : 到 android repository 下載並解壓縮 platform-tools.zip (經過測試只有platform-tools_r20-linux.zip 可用),替換掉原本無法使用的platform-tools 資料夾即可。
 

分類
Uncategorized

隱私權政策網址

隱私權政策

非常歡迎您使用本APP,為了讓您能夠安心的使用各項服務與資訊,特此向您說明本APP的隱私權保護政策,以保障您的權益,請您詳閱下列內容:
一、隱私權保護政策的適用範圍
隱私權保護政策內容,包括本APP如何處理在您使用網站服務時收集到的個人識別資料。隱私權保護政策不適用於本APP以外的相關連結網站,也不適用於非本APP所委託或參與管理的人員。
二、個人資料的蒐集、處理及利用方式
當您洽辦本APP業務或參與本APP活動時,我們將視業務或活動性質請您提供必要的個人資料,並在該特定目的範圍內處理及利用您的個人資料;非經您書面同意,本APP不會將個人資料用於其他用途。
本APP在您使用服務信箱、問卷調查等互動性功能時,會保留您所提供的姓名、電子郵件地址、聯絡方式及使用時間等。
於一般瀏覽時,伺服器會自行記錄相關行徑,包括您使用連線設備的IP位址、使用時間、使用的瀏覽器、瀏覽及點選資料記錄等,做為我們增進網站服務的參考依據,此記錄為內部應用,決不對外公佈。
為提供精確的服務,我們會將收集的問卷調查內容進行統計與分析,分析結果之統計數據或說明文字呈現,除供內部研究外,我們會視需要公佈統計數據及說明文字,但不涉及特定個人之資料。
三、資料之保護
本APP主機均設有防火牆、防毒系統等相關的各項資訊安全設備及必要的安全防護措施,加以保護網站及您的個人資料採用嚴格的保護措施,只由經過授權的人員才能接觸您的個人資料,相關處理人員皆簽有保密合約,如有違反保密義務者,將會受到相關的法律處分。
如因業務需要有必要委託其他單位提供服務時,本APP亦會嚴格要求其遵守保密義務,並且採取必要檢查程序以確定其將確實遵守。
四、網站對外的相關連結
本APP的網頁提供其他網站的網路連結,您也可經由本APP所提供的連結,點選進入其他網站。但該連結網站不適用本APP的隱私權保護政策,您必須參考該連結網站中的隱私權保護政策。
五、與第三人共用個人資料之政策
本APP絕不會提供、交換、出租或出售任何您的個人資料給其他個人、團體、私人企業或公務機關,但有法律依據或合約義務者,不在此限。
前項但書之情形包括不限於:
*經由您書面同意。
*法律明文規定。
*為免除您生命、身體、自由或財產上之危險。
*與公務機關或學術研究機構合作,基於公共利益為統計或學術研究而有必要,且資料經過提供者處理或蒐集著依其揭露方式無從識別特定之當事人。
*當您在網站的行為,違反服務條款或可能損害或妨礙網站與其他使用者權益或導致任何人遭受損害時,經網站管理單位研析揭露您的個人資料是為了辨識、聯絡或採取法律行動所必要者。
*有利於您的權益。
*本APP委託廠商協助蒐集、處理或利用您的個人資料時,將對委外廠商或個人善盡監督管理之責。
六、Cookie之使用
為了提供您最佳的服務,本APP會在您的電腦中放置並取用我們的Cookie,若您不願接受Cookie的寫入,您可在您使用的瀏覽器功能項中設定隱私權等級為高,即可拒絕Cookie的寫入,但可能會導至網站某些功能無法正常執行 。
七、隱私權保護政策之修正
本APP隱私權保護政策將因應需求隨時進行修正,修正後的條款將刊登於網站上。