官網教學,參考 https://developer.android.com/training/testing.html
這裡為紀錄詳細步驟。
Target Class : RoundAmountSaving也就是要測試的Android app(被測目標),該app能幫你計算本金和,你只需要輸入目前儲蓄金額,年利率,以及儲蓄月數
它就能算出儲蓄多久可得到多少錢。
裡面的元件組成相當簡單,只有幾個EditText,Button,一個簡單的計算公式。

package com.foxx.round_amount_saving;
import com.foxx_round_amount_saving.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class RoundAmountSaving extends Activity
{
    private static final int PERCENT = 100;
    private static final int MONTH_COUNT = 12;
    private EditText mInputLend;
    private EditText mInputRate;
    private EditText mInputNumber;
    private Button mSubmit;
    private TextView mAmount;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initLayoutComponents();
    }
    private void initLayoutComponents()
    {
        mInputLend = (EditText) findViewById(R.id.input_lend);
        mInputNumber = (EditText) findViewById(R.id.input_number);
        mInputRate = (EditText) findViewById(R.id.input_rate);
        mSubmit = (Button) findViewById(R.id.submit);
        mSubmit.setOnClickListener(getClickListenerForSubmit());
        mAmount = (TextView) findViewById(R.id.amount);
    }
    private OnClickListener getClickListenerForSubmit()
    {
        return new OnClickListener() {
            @Override
            public void onClick(View v)
            {
                mAmount.setText("" + countAmount());
            }
        };
    }
    private double countAmount()
    {
        int lend = Integer.parseInt(mInputLend.getText().toString());
        double rateOfMonth = Double.parseDouble(mInputRate.getText().toString()) / MONTH_COUNT
                / PERCENT;
        int number = Integer.parseInt(mInputNumber.getText().toString());
        return lend * (Math.pow(rateOfMonth + 1, number));
    }
}

 
開始建立Android Test Project

1. Eclipse -> File -> New -> Other -> type : android test project -> next

 

2. type : RoundAmountSavingTest in project name -> next

 

3. select test target : RoundAmountSaving -> Finish

Note : RoundAmountSaving 是 project Name
完成後會產生android test project,名稱為 RoundAmountSavingTest,其中有設定好的package,接著開始來寫 test case
 

4.建立 junit test case

Eclipse -> File -> New -> Other -> type : junit test case -> next
 

5. 選擇 New Junit 3 test,輸入Name,並選擇Class under test : com.foxx.roundamountsaving.RoundAmountSaving -> Finish

 

6.修改RoundAmountSavingTest.java

由於RoundAmountSaving繼承Activity,為了測試Activity我們必須讓Test Case繼承ActivityInstrumentationTestCase2並呼叫父類別建構式

package com.foxx.roundamountsaving.test;
import android.test.ActivityInstrumentationTestCase2;
import com.foxx.roundamountsaving.RoundAmountSaving;
public class RoundAmountSavingTest extends ActivityInstrumentationTestCase2<RoundAmountSaving>
{
    public RoundAmountSavingTest(){
        super(RoundAmountSaving.class);
    }
}

 
接著override setUp方法,並在方法中設定相關內容,包含測試類別的實體

package com.foxx.roundamountsaving.test;
import android.test.ActivityInstrumentationTestCase2;
import com.foxx.roundamountsaving.RoundAmountSaving;
public class RoundAmountSavingTest extends ActivityInstrumentationTestCase2<RoundAmountSaving>
{
    private RoundAmountSaving mRoundAmountSaving;
    public RoundAmountSavingTest(){
        super(RoundAmountSaving.class);
    }
    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        setActivityInitialTouchMode(true);
        mRoundAmountSaving = getActivity();
    }
}

 
可以開始寫測試方法了,為了確保測試資料的完整性,先測試mRoundAmountSaving不可為null

public void testRoundAmountSavingNotNull(){
        assertNotNull(mRoundAmountSaving);
}

 
Run test!!!
加入RoundAmountSaving上的UI元件,測試這些元件的layout或是功能性,
預設情況在38~42行會出現 id cannot be resolved or is not a field 的錯誤,Eclipse 並不會自動修復,我們必須手動import RoundAmountSaving的R(第11行)

package com.foxx.roundamountsaving.test;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.foxx.roundamountsaving.RoundAmountSaving;
import com.foxx.roundamountsaving.R;
public class RoundAmountSavingTest extends ActivityInstrumentationTestCase2<RoundAmountSaving>
{
    private RoundAmountSaving mRoundAmountSaving;
    private EditText mInputLend;
    private EditText mInputRate;
    private EditText mInputNumber;
    private Button mSubmit;
    private TextView mAmount;
    public RoundAmountSavingTest(){
        super(RoundAmountSaving.class);
    }
    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        setActivityInitialTouchMode(true);
        mRoundAmountSaving = getActivity();
        initLayoutComponents();
    }
    public void testRoundAmountSavingNotNull(){
        assertNotNull(mRoundAmountSaving);
    }
    private void initLayoutComponents()
    {
        mInputLend = (EditText) mRoundAmountSaving.findViewById(R.id.input_lend);
        mInputNumber = (EditText) mRoundAmountSaving.findViewById(R.id.input_number);
        mInputRate = (EditText) mRoundAmountSaving.findViewById(R.id.input_rate);
        mSubmit = (Button) mRoundAmountSaving.findViewById(R.id.submit);
        mAmount = (TextView) mRoundAmountSaving.findViewById(R.id.amount);
    }
}

 
加入UI元件不可為null的測試

public void testLayoutComponentsNotNull()
{
        assertNotNull(mInputLend);
        assertNotNull(mInputNumber);
        assertNotNull(mInputRate);
        assertNotNull(mSubmit);
}

 
Run test~
加入測試UI元件layout的測試,assertEquals的第2個參數必須參考RoundAmountSaving的UI xml的設定

public void testAmountLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mAmount.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.WRAP_CONTENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
    public void testInputNumberLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mInputNumber.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
    public void testInputRateLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mInputRate.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
    public void testInputLendLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mInputLend.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
    public void testSubmitLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mSubmit.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }

 
Run test~
為了測試UI元件的功能性,加入requestFocusLayoutComponent和setValueToLayoutComponent2個方法,
requestFocusLayoutComponent可讓UI元件取得焦點,取得焦點後再呼叫setValueToLayoutComponent這個方法可以在edittext中設定數值

private void requestFocusLayoutComponent(final View component)
    {
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run()
            {
                component.requestFocus();
            }
        });
    }
private void setValueToLayoutComponent(String value)
    {
        getInstrumentation().waitForIdleSync();
        getInstrumentation().sendStringSync(value);
        getInstrumentation().waitForIdleSync();
    }

 
設定數值到各UI元件

private void inputTestValueInInputNumber()
    {
        requestFocusLayoutComponent(mInputNumber);
        setValueToLayoutComponent("20");
    }
    private void inputTestValueInInputRate()
    {
        requestFocusLayoutComponent(mInputRate);
        setValueToLayoutComponent("10");
    }
    private void inputTestValueInInputLend()
    {
        requestFocusLayoutComponent(mInputLend);
        setValueToLayoutComponent("100");
    }

 
 
最後加入測試計算的正確性,expectedCountAmountResult 為預期的計算結果,先設定數值(inutTestValueInInputXXX)到各UI元件中
再呼叫TouchUtils.clickView模擬mSubmit被按下,最後從mAmount取得計算結果和expectedCountAmountResult比較是否相等
相等的話代表算法正確

public void testCountAmount()
    {
        final double expectedCountAmountResult = 118.05448347663095;
        inputTestValueInInputLend();
        inputTestValueInInputRate();
        inputTestValueInInputNumber();
        TouchUtils.clickView(this, mSubmit);
        String result = (String) mAmount.getText();
        assertEquals(result, "" + expectedCountAmountResult);
    }

 
Run test~
其實在測試算法還可以加入更多的測試條件,如邊界值,特殊值等等
final RoundAmountSavingTest.java

package com.foxx.roundamountsaving.test;
import android.test.ActivityInstrumentationTestCase2;
import android.test.TouchUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.foxx.roundamountsaving.RoundAmountSaving;
import com.foxx.roundamountsaving.R;
public class RoundAmountSavingTest extends ActivityInstrumentationTestCase2<RoundAmountSaving>
{
    private RoundAmountSaving mRoundAmountSaving;
    private EditText mInputLend;
    private EditText mInputRate;
    private EditText mInputNumber;
    private Button mSubmit;
    private TextView mAmount;
    public RoundAmountSavingTest(){
        super(RoundAmountSaving.class);
    }
    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        setActivityInitialTouchMode(true);
        mRoundAmountSaving = getActivity();
        initLayoutComponents();
    }
    public void testRoundAmountSavingNotNull(){
        assertNotNull(mRoundAmountSaving);
    }
    public void testLayoutComponentsNotNull()
    {
        assertNotNull(mInputLend);
        assertNotNull(mInputNumber);
        assertNotNull(mInputRate);
        assertNotNull(mSubmit);
    }
    private void initLayoutComponents()
    {
        mInputLend = (EditText) mRoundAmountSaving.findViewById(R.id.input_lend);
        mInputNumber = (EditText) mRoundAmountSaving.findViewById(R.id.input_number);
        mInputRate = (EditText) mRoundAmountSaving.findViewById(R.id.input_rate);
        mSubmit = (Button) mRoundAmountSaving.findViewById(R.id.submit);
        mAmount = (TextView) mRoundAmountSaving.findViewById(R.id.amount);
    }
    private void requestFocusLayoutComponent(final View component)
    {
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run()
            {
                component.requestFocus();
            }
        });
    }
    private void setValueToLayoutComponent(String value)
    {
        getInstrumentation().waitForIdleSync();
        getInstrumentation().sendStringSync(value);
        getInstrumentation().waitForIdleSync();
    }
    public void testCountAmount()
    {
        final double expectedCountAmountResult = 118.05448347663095;
        inputTestValueInInputLend();
        inputTestValueInInputRate();
        inputTestValueInInputNumber();
        TouchUtils.clickView(this, mSubmit);
        String result = (String) mAmount.getText();
        assertEquals(result, "" + expectedCountAmountResult);
    }
    private void inputTestValueInInputNumber()
    {
        requestFocusLayoutComponent(mInputNumber);
        setValueToLayoutComponent("20");
    }
    private void inputTestValueInInputRate()
    {
        requestFocusLayoutComponent(mInputRate);
        setValueToLayoutComponent("10");
    }
    private void inputTestValueInInputLend()
    {
        requestFocusLayoutComponent(mInputLend);
        setValueToLayoutComponent("100");
    }
    public void testAmountLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mAmount.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.WRAP_CONTENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
    public void testInputNumberLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mInputNumber.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
    public void testInputRateLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mInputRate.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
    public void testInputLendLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mInputLend.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
    public void testSubmitLayoutValid()
    {
        final ViewGroup.LayoutParams layoutParams = mSubmit.getLayoutParams();
        assertNotNull(layoutParams);
        assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
        assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);
    }
}