Dict.CN 在线词典, 英语学习, 在线翻译

都市淘沙者

荔枝FM Everyone can be host

统计

留言簿(23)

积分与排名

优秀学习网站

友情连接

阅读排行榜

评论排行榜

JUnit 和 Android JUnit [转]

http://blog.csdn.net/partner4java/archive/2010/06/03/5644545.aspx
因为最近学习TDD,所以JUnit是必须要会的,在这里整理一下,发出来。
什么是TDD:

Test Driven Development
--测试驱动开发
别人说的一些话:
TDD就是把你的需求用测试给描述出来。
没有Mock的单元测试就很可能不是单元测试,也许是集成测试,也许是功能测试,总之不是TDD中所需要的那种能够驱动你开发的测试。
和传统开发方法里的详细设计不同,写一个单元测试,就写一段代码让它通过。这样你就不需要在实现的时候,再去读文档,再去回忆当时是怎么想的,能提高效率;更重要的是,这个“文档”是能反复运行的,可以保证和实现的一致性。


Robert C. Martin:
1.除非这能让失败的单元测试通过,否则不允许去编写任何的产品代码。
2.只允许编写刚好能够导致失败的单元测试。 (编译失败也属于一种失败)
3.只允许编写刚好能够导致一个失败的单元测试通过的产品代码。
对于任何功能,一定要从编写它的单元测试开始;但是到了原则2,你就不能再为那个单元测试写更多内容。只要一出现该单元测试代码编译失败,或是断言失败,你就必须停下来开始编写产品代码;但是到了原则3,你就只能编写产品代码,直到让测试编译成功或通过断言为准。

单元测试三条规则:
每个单元测试都必须独立于其他单元测试而运行。
必须以单元测试为单位来检测和报告错误。
必须易于定于要运行哪些单元测试。

当你需要编写更多的test case的时候,你可以创建更多的TestCase对象。当你需要一次执行多个TestCase对象的时候,你可以创建另一个叫做TestSuite的对象。为了执行TestSuite,你需要使用TestRunner。

TestCase 
+ TestSuite + BaseTestRunner = TestResult
Test runner:
JUnit发布包中包含了3个TestRunner类:一个用户文本控制台,一个用户Swing,甚至还有一个AWT。
并不存在TestRunner接口,这和JUnit框架中的其它元素不同。相反,JUnit附带的几个test runner都是继承自BaseTestRunner。如果你不管什么理由需要编写你自己的test runner,你也可以继承这个类。例如:Cactus框架就继承了BaseTestRunner来创建一个ServletTestRunner,可以从浏览器中运行JUnit测试。

用TestSuite来组合测试:
若想要运行多个test case又会如何呢?如果想在多个test case中选一部分又怎样呢?应当如何来为test case分组呢?
看来,在TestCase和TestRunner之间,你还需要某种容器,用来把几个测试归在一起,并且把他们作为一个集合一起运行,但是,在使得多个test case运行得以简化的同时,你也不希望让运行单个test case变得复杂化。
JUnit对这个难题的回答是TestSuite。TestSuite被设计成可以运行一个或多个test 
case,test runner负责启动TestSuite,而要运行哪些test case则由TestSuite来决定。
若你没有提供自己的TestSuite,test runner会自动创建一个。
缺省的TestSuite会扫描你的测试类,找出所有以test开头的方法。缺省的TestSuite在内部为每个testXXX方法都创建一个TestSuite的实例,要调用的方法的名字会传递给TestCase的构造函数,这样每个实例就有一个独一无二的标识。
用TestResult来收集参数:
TestResult负责收集TestCase的执行结果。如果所有的测试都总是可以顺利的完成,那么为什么还要运行他们呢?所以,TestResult储存了所有测试的详细情况,是通过还是失败。
TestRunner使用TestResult来报告测试结果。如果TestResult集合中没有TestFailure对象,那么代码就是干净的,进度条就的绿色的;否则TestRunner就会报失败,并输出失败测试的数目和它们的stack trace。
你只需要知道他的存在就OK了。
用TestListener来观察结果:
JUnit框架提供了TestListener接口,以帮组对象访问TestResult并创建有用的报告。TestRunner实现了 TestListener,很多特定的JUnit扩展和实现了TestListener。可以有任意数量的TestLintener向JUnit框架注册,这些TestListener可以根据TestResult提供的信息做它们需要做的任何事情。
用TestCase来工作:
概括地说,JUnit的工作过程就是由TestRunner来运行包含一个或者多个TestCase(或者其他TestSuite)的TestSuite。但是在常规的工作中,你通常只和TestCase打交道。

android对我们的JUnit进行了扩展:

runner:
AndroidTestRunner  
extends BaseTestRunner :

demo:

package cn.partner4java;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

/**
* 主要的入口,负责显示button,点击button后触发测试事件,显示测试信息
@author partner4java
*
*/
public class MainActivity extends Activity {
    
private static final String TAG = "JUnit";
    
private Thread mRuner ;
  
    @Override
    
public void onCreate(Bundle savedInstanceState) {
        
super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
      
        ((Button) 
this.findViewById(R.id.start_junit)).setOnClickListener(
                
new View.OnClickListener(){
                    
public void onClick(View v) {
                        startJUnit();
                    }
                }
        );
    }
    
/**
     * on click start all JUnit
     
*/
    
protected void startJUnit() {
        
if(mRuner == null){
            Log.d(TAG, 
"startJUnit");
            mRuner 
= new Thread(new JUnitRunner(this));
            mRuner.start();
        }
    }
}


上面:通过触摸点击事件,启动一个新线程

package cn.partner4java;

import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestListener;
import android.app.Activity;
import android.test.AndroidTestRunner;
import android.util.Log;

/**
* 首先我是一个runner,然后我实现了TestListener,我可以监听JUnit处理错误
@author partner4java
*
*/
public class JUnitRunner implements Runnable,TestListener {
    
private static final String TAG = "JUnit";
    
private Activity mActivity;
  
    
private int mCount;
    
private int mErrorCount;
    
private int mFailureCount;
  
    
private AlterShowText mAlterShowText;
  
    
public JUnitRunner(Activity activity) {
        mActivity 
= activity;
    }

    
public void run() {
        Log.d(TAG, 
"JUnitRunner run");
        mCount 
= 0;
        mErrorCount 
= 0;
        mFailureCount 
= 0;
        AndroidTestRunner androidTestRunner 
= new AndroidTestRunner();
        androidTestRunner.setContext(mActivity);
        androidTestRunner.addTestListener(
this);
        androidTestRunner.setTest(
new AllSuite());
        androidTestRunner.runTest();
        Log.d(TAG, 
"run my androidTestRunner");
    }

    
public void addError(Test test, Throwable t) {
        
++mErrorCount;
        Log.d(TAG, 
"addError className:" + test.getClass().getName());
        Log.d(TAG, t.getMessage(), t);
        mAlterShowText 
= new AlterShowText(mActivity,"error:" + test.getClass().getName(),mCount,mErrorCount,mFailureCount);
        mActivity.runOnUiThread(mAlterShowText);
    }

    
public void addFailure(Test test, AssertionFailedError t) {
        Log.d(TAG, 
"addFailure className:" + test.getClass().getName());
        Log.d(TAG, t.getMessage(), t);
        
++mFailureCount;
        mAlterShowText 
= new AlterShowText(mActivity,"failure:" + test.getClass().getName(),mCount,mErrorCount,mFailureCount);
        mActivity.runOnUiThread(mAlterShowText);
    }

    
public void endTest(Test test) {
        Log.d(TAG, 
"endTest className:" + test.getClass().getName());
        mAlterShowText 
= new AlterShowText(mActivity,"end:" + test.getClass().getName(),mCount,mErrorCount,mFailureCount);
        mActivity.runOnUiThread(mAlterShowText);
    }

    
public void startTest(Test test) {
        Log.d(TAG, 
"startTest className:" + test.getClass().getName());
        
++mCount;
        mAlterShowText 
= new AlterShowText(mActivity,"start:" + test.getClass().getName(),mCount,mErrorCount,mFailureCount);
        mActivity.runOnUiThread(mAlterShowText);
    }

}

我们线程里面run了一个runner:

AndroidTestRunner androidTestRunner 
= new AndroidTestRunner();
        androidTestRunner.setContext(mActivity);
        androidTestRunner.addTestListener(
this);
        androidTestRunner.setTest(
new AllSuite());
        androidTestRunner.runTest();

这个runner注册了test suite 和 listener,这个listner会监视四个事件:addError\addFailure\endTest\startTest,四个事件。也就是说,我们runner去启动了一个test suite,然后注册了一个监听,去监听每次执行test不同的场景。

package cn.partner4java;

import cn.partner4java.test.MyTest;
import junit.framework.TestSuite;

/**
* 测试集合
@author partner4java
*
*/
public class AllSuite extends TestSuite {
    
/**
     * 按照以前的思想,我这里原来添加的是 suite 方法,但是不对。。
     
*/
    
public AllSuite(){
        addTestSuite(MyTest.
class);
    }
}

package cn.partner4java;

import android.app.Activity;
import android.util.Log;
import android.widget.TextView;

/**
* 我是一个线程,如果想改变界面显示,ok,把我new 出来,然后运行近界面显示线程
@author partner4java
*
*/
public class AlterShowText implements Runnable {
    
private static final String TAG = "JUnit";
    
private String mName;
    
private int mCount;
    
private int mErrorCount;
    
private int mFailureCount;
    
private Activity mActivity;
    
private TextView mCurrentJUnit;
    
private TextView mCurrentCount;
    
private TextView mCurrentErrorCount;
    
private TextView mCurrentFailureCount;
  
    
public AlterShowText(Activity activity,String name, int count, int errorCount, int failureCount) {
        Log.d(TAG, 
"AlterShowText public mName:" + name);
        mActivity 
= activity;
        mCurrentJUnit 
= (TextView) mActivity.findViewById(R.id.current_JUnit);
        mCurrentCount 
= (TextView) mActivity.findViewById(R.id.current_count);
        mCurrentErrorCount 
= (TextView) mActivity.findViewById(R.id.current_error_count);
        mCurrentFailureCount 
= (TextView) mActivity.findViewById(R.id.current_failure_count);
        mName 
= name;
        mCount 
= count;
        mErrorCount 
= errorCount;
        mFailureCount 
= failureCount;
    }

    
public void run() {
        
if(mName != null){
            Log.d(TAG, 
"AlterShowText run");
            mCurrentJUnit.setText(mName);
            mCurrentCount.setText(String.valueOf(mCount));
            mCurrentErrorCount.setText(String.valueOf(mErrorCount));
            mCurrentFailureCount.setText(String.valueOf(mFailureCount));
        }  
    }
  
}
事件触发后会执行这个类去改变界面的显示,需要注意的是: mActivity.runOnUiThread(mAlterShowText);监听里面的界面修改是回到UI线程的,因为只有在UI线程才可以修改界面的显示。

package cn.partner4java.test;

import android.util.Log;
import junit.framework.TestCase;

/**
* 单元测试
@author partner4java
*
*/
public class MyTest extends TestCase {
  
    
private static final String TAG = "JUnit";

    
public void testAdd(){
        Log.d(TAG, 
"MyTest testAdd");
    }
}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation
="vertical"
    android:layout_width
="fill_parent"
    android:layout_height
="fill_parent"
    
>
<TextView 
    android:layout_width
="fill_parent"
    android:layout_height
="wrap_content"
    android:text
="@string/hello"
    
/>
   
    
<Button
        android:id
="@+id/start_junit"
        android:layout_width
="wrap_content"
        android:layout_height
="wrap_content"
        android:text
="StartJUnit"/>
    
<TextView
        android:id
="@+id/current_JUnit"
        android:layout_width
="fill_parent"
        android:layout_height
="wrap_content"
        android:text
=""/>
    
<TextView
        android:id
="@+id/current_count"
        android:layout_width
="fill_parent"
        android:layout_height
="wrap_content"
        android:text
=""/>
       
    
<TextView
        android:id
="@+id/current_error_count"
        android:layout_width
="fill_parent"
        android:layout_height
="wrap_content"
        android:text
=""/>
    
<TextView
        android:id
="@+id/current_failure_count"
        android:layout_width
="fill_parent"
        android:layout_height
="wrap_content"
        android:text
=""/>                       
   
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      
package="cn.partner4java"
      android:versionCode
="1"
      android:versionName
="1.0">
    
<application android:icon="@drawable/icon" android:label="@string/app_name">
        
<activity android:name=".MainActivity"
                  android:label
="@string/app_name">
            
<intent-filter>
                
<action android:name="android.intent.action.MAIN" />
                
<category android:name="android.intent.category.LAUNCHER" />
            
</intent-filter>
        
</activity>
        
<uses-library android:name="android.test.runner"/>
    
</application>
</manifest>


InstrumentationTestRunner 
extends Instrumentation
implements TestSuiteProvider :

这个并没有像我们想象的那样去继承 BaseTestRunner ,那么好让我们看看Instrumentation

是做什么的呢?

Base 
class for implementing application instrumentation code. When running with instrumentation turned on, this class will be instantiated for you before any of the application code, allowing you to monitor all of the interaction the system has with the application. An Instrumentation implementation is described to the system through an AndroidManifest.xml's <instrumentation> tag.

原来是一个自动监听器,估计作用和我们上面的lintener一样,只是这里为什么没用listener估计这里不是简简单单的对虚拟机监听,是android环境的特殊监听,所以就自己搞了个


在AndroidMainfest文件添加:
<instrumentation android:name="android.test.InstrumentationTestRunner"
                     android:targetPackage
="cn.partner4java"
                     android:label
="Tests for Api Demos."/>

name就算runner,targetPackage是我们要测试的包名

常用的test 
case

          
* ActivityInstrumentationTestCase2
           
* ActivityUnitTestCase
           
* AndroidTestCase
           
* ApplicationTestCase
           
* InstrumentationTestCase
           
* ProviderTestCase
           
* ServiceTestCase
           
* SingleLaunchActivityTestCase

当然这几个test case各具特色,应用场景略有不同。

runner如何运行test 
case

   
1. Run the instrumentation using "adb shell am instrument -w", with no optional arguments, to run all tests (except performance tests).
   
2. Run the instrumentation using "adb shell am instrument -w", with the argument '-e func true' to run all functional tests. These are tests that derive from InstrumentationTestCase .
   
3. Run the instrumentation using "adb shell am instrument -w", with the argument '-e unit true' to run all unit tests. These are tests that do not derive from InstrumentationTestCase (and are not performance tests).
   
4. Run the instrumentation using "adb shell am instrument -w", with the argument '-e class' set to run an individual TestCase .

Running all tests: adb shell am instrument 
-w com.android.foo/android.test.InstrumentationTestRunner

Running all small tests: adb shell am instrument 
--e size small com.android.foo/android.test.InstrumentationTestRunner

Running all medium tests: adb shell am instrument 
--e size medium com.android.foo/android.test.InstrumentationTestRunner

Running all large tests: adb shell am instrument 
--e size large com.android.foo/android.test.InstrumentationTestRunner

Filter test run to tests with given annotation: adb shell am instrument 
--e annotation com.android.foo.MyAnnotation com.android.foo/android.test.InstrumentationTestRunner

If used with other options, the resulting test run will contain the union of the two options. e.g. 
"-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both the LargeTest and "com.android.foo.MyAnnotation" annotations.

Filter test run to tests without given annotation: adb shell am instrument 
--e notAnnotation com.android.foo.MyAnnotation com.android.foo/android.test.InstrumentationTestRunner

Running a single testcase: adb shell am instrument 
--class com.android.foo.FooTest com.android.foo/android.test.InstrumentationTestRunner

Running a single test: adb shell am instrument 
--class com.android.foo.FooTest#testFoo com.android.foo/android.test.InstrumentationTestRunner

Running multiple tests: adb shell am instrument 
--class com.android.foo.FooTest,com.android.foo.TooTest com.android.foo/android.test.InstrumentationTestRunner

Running all tests in a java 
package: adb shell am instrument --package com.android.foo.subpkg com.android.foo/android.test.InstrumentationTestRunner

Including performance tests: adb shell am instrument 
--e perf true com.android.foo/android.test.InstrumentationTestRunner

To debug your tests, set a 
break point in your code and pass: -e debug true

To run in 
'log only' mode -e log true This option will load and iterate through all test classes and methods, but will bypass actual test execution. Useful for quickly obtaining info on the tests to be executed by an instrumentation command.

To generate EMMA code coverage: 
-e coverage true Note: this requires an emma instrumented build. By default, the code coverage results file will be saved in a /data//coverage.ec file, unless overridden by coverageFile flag (see below)

To specify EMMA code coverage results file path: 
-e coverageFile /sdcard/myFile.ec
in addition to the other arguments.

还有,当我们需要写测试代码时,可以格外的创建测试工程,只要创建的时候选折对那个工程进行测试就OK了

补一个例子:8里面多了这个demo,感觉对 ActivityInstrumentationTestCase2用的比较经典了

首先项目里就这么一个activity:

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      
http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 
*/

package com.android.example.spinner;

import com.android.example.spinner.R;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;

/**
 * Displays an Android spinner widget backed by data in an array. The
 * array is loaded from the strings.xml resources file.
 
*/
public class SpinnerActivity extends Activity {

    
/**
     * Fields to contain the current position and display contents of the spinner
     
*/
    
protected int mPos;
    
protected String mSelection;

    
/**
     * ArrayAdapter connects the spinner widget to array-based data.
     
*/
    
protected ArrayAdapter<CharSequence> mAdapter;

    
/**
     *  The initial position of the spinner when it is first installed.
     
*/
    
public static final int DEFAULT_POSITION = 2;

    
/**
     * The name of a properties file that stores the position and
     * selection when the activity is not loaded.
     
*/
    
public static final String PREFERENCES_FILE = "SpinnerPrefs";

    
/**
     * These values are used to read and write the properties file.
     * PROPERTY_DELIMITER delimits the key and value in a Java properties file.
     * The "marker" strings are used to write the properties into the file
     
*/
    
public static final String PROPERTY_DELIMITER = "=";

    
/**
     * The key or label for "position" in the preferences file
     
*/
    
public static final String POSITION_KEY = "Position";

    
/**
     * The key or label for "selection" in the preferences file
     
*/
    
public static final String SELECTION_KEY = "Selection";

    
public static final String POSITION_MARKER =
            POSITION_KEY 
+ PROPERTY_DELIMITER;

    
public static final String SELECTION_MARKER =
            SELECTION_KEY 
+ PROPERTY_DELIMITER;

    
/**
     * Initializes the application and the activity.
     * 1) Sets the view
     * 2) Reads the spinner's backing data from the string resources file
     * 3) Instantiates a callback listener for handling selection from the
     *    spinner
     * Notice that this method includes code that can be uncommented to force
     * tests to fail.
     *
     * This method overrides the default onCreate() method for an Activity.
     *
     * 
@see android.app.Activity#onCreate(android.os.Bundle)
     
*/
    @Override
    
public void onCreate(Bundle savedInstanceState) {

        
/**
         * derived classes that use onCreate() overrides must always call the super constructor
         
*/
        
super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        Spinner spinner 
= (Spinner) findViewById(R.id.Spinner01);

        
/*
         * Create a backing mLocalAdapter for the Spinner from a list of the
         * planets. The list is defined by XML in the strings.xml file.
         
*/

        
this.mAdapter = ArrayAdapter.createFromResource(this, R.array.Planets,
                android.R.layout.simple_spinner_dropdown_item);

        
/*
         * Attach the mLocalAdapter to the spinner.
         
*/

        spinner.setAdapter(
this.mAdapter);

        
/*
         * Create a listener that is triggered when Android detects the
         * user has selected an item in the Spinner.
         
*/

        OnItemSelectedListener spinnerListener 
= new myOnItemSelectedListener(this,this.mAdapter);

        
/*
         * Attach the listener to the Spinner.
         
*/

        spinner.setOnItemSelectedListener(spinnerListener);


        
/*
         * To demonstrate a failure in the preConditions test,
         * uncomment the following line.
         * The test will fail because the selection listener for the
         * Spinner is not set.
         
*/
         
// spinner.setOnItemSelectedListener(null);

    }


    
/**
     *  A callback listener that implements the
     *  {
@link android.widget.AdapterView.OnItemSelectedListener} interface
     *  For views based on adapters, this interface defines the methods available
     *  when the user selects an item from the View.
     *
     
*/
    
public class myOnItemSelectedListener implements OnItemSelectedListener {

        
/*
         * provide local instances of the mLocalAdapter and the mLocalContext
         
*/

        ArrayAdapter
<CharSequence> mLocalAdapter;
        Activity mLocalContext;

        
/**
         *  Constructor
         *  
@param c - The activity that displays the Spinner.
         *  
@param ad - The Adapter view that
         *    controls the Spinner.
         *  Instantiate a new listener object.
         
*/
        
public myOnItemSelectedListener(Activity c, ArrayAdapter<CharSequence> ad) {

          
this.mLocalContext = c;
          
this.mLocalAdapter = ad;

        }

        
/**
         * When the user selects an item in the spinner, this method is invoked by the callback
         * chain. Android calls the item selected listener for the spinner, which invokes the
         * onItemSelected method.
         *
         * 
@see android.widget.AdapterView.OnItemSelectedListener#onItemSelected(
         *  android.widget.AdapterView, android.view.View, int, long)
         * 
@param parent - the AdapterView for this listener
         * 
@param v - the View for this listener
         * 
@param pos - the 0-based position of the selection in the mLocalAdapter
         * 
@param row - the 0-based row number of the selection in the View
         
*/
        
public void onItemSelected(AdapterView<?> parent, View v, int pos, long row) {

            SpinnerActivity.
this.mPos = pos;
            SpinnerActivity.
this.mSelection = parent.getItemAtPosition(pos).toString();
            
/*
             * Set the value of the text field in the UI
             
*/
            TextView resultText 
= (TextView)findViewById(R.id.SpinnerResult);
            resultText.setText(SpinnerActivity.
this.mSelection);
        }

        
/**
         * The definition of OnItemSelectedListener requires an override
         * of onNothingSelected(), even though this implementation does not use it.
         * 
@param parent - The View for this Listener
         
*/
        
public void onNothingSelected(AdapterView<?> parent) {

            
// do nothing

        }
    }

    
/**
     * Restores the current state of the spinner (which item is selected, and the value
     * of that item).
     * Since onResume() is always called when an Activity is starting, even if it is re-displaying
     * after being hidden, it is the best place to restore state.
     *
     * Attempts to read the state from a preferences file. If this read fails,
     * assume it was just installed, so do an initialization. Regardless, change the
     * state of the spinner to be the previous position.
     *
     * 
@see android.app.Activity#onResume()
     
*/
    @Override
    
public void onResume() {

        
/*
         * an override to onResume() must call the super constructor first.
         
*/

        
super.onResume();

        
/*
         * Try to read the preferences file. If not found, set the state to the desired initial
         * values.
         
*/

        
if (!readInstanceState(this)) setInitialState();

        
/*
         * Set the spinner to the current state.
         
*/

        Spinner restoreSpinner 
= (Spinner)findViewById(R.id.Spinner01);
        restoreSpinner.setSelection(getSpinnerPosition());

    }

    
/**
     * Store the current state of the spinner (which item is selected, and the value of that item).
     * Since onPause() is always called when an Activity is about to be hidden, even if it is about
     * to be destroyed, it is the best place to save state.
     *
     * Attempt to write the state to the preferences file. If this fails, notify the user.
     *
     * 
@see android.app.Activity#onPause()
     
*/
    @Override
    
public void onPause() {

        
/*
         * an override to onPause() must call the super constructor first.
         
*/

        
super.onPause();

        
/*
         * Save the state to the preferences file. If it fails, display a Toast, noting the failure.
         
*/

        
if (!writeInstanceState(this)) {
             Toast.makeText(
this,
                     
"Failed to write state!", Toast.LENGTH_LONG).show();
          }
    }

    
/**
     * Sets the initial state of the spinner when the application is first run.
     
*/
    
public void setInitialState() {

        
this.mPos = DEFAULT_POSITION;

    }

    
/**
     * Read the previous state of the spinner from the preferences file
     * 
@param c - The Activity's Context
     
*/
    
public boolean readInstanceState(Context c) {

        
/*
         * The preferences are stored in a SharedPreferences file. The abstract implementation of
         * SharedPreferences is a "file" containing a hashmap. All instances of an application
         * share the same instance of this file, which means that all instances of an application
         * share the same preference settings.
         
*/

        
/*
         * Get the SharedPreferences object for this application
         
*/

        SharedPreferences p 
= c.getSharedPreferences(PREFERENCES_FILE, MODE_WORLD_READABLE);
        
/*
         * Get the position and value of the spinner from the file, or a default value if the
         * key-value pair does not exist.
         
*/
        
this.mPos = p.getInt(POSITION_KEY, SpinnerActivity.DEFAULT_POSITION);
        
this.mSelection = p.getString(SELECTION_KEY, "");

        
/*
         * SharedPreferences doesn't fail if the code tries to get a non-existent key. The
         * most straightforward way to indicate success is to return the results of a test that
         * SharedPreferences contained the position key.
         
*/

          
return (p.contains(POSITION_KEY));

        }

    
/**
     * Write the application's current state to a properties repository.
     * 
@param c - The Activity's Context
     *
     
*/
    
public boolean writeInstanceState(Context c) {

        
/*
         * Get the SharedPreferences object for this application
         
*/

        SharedPreferences p 
=
                c.getSharedPreferences(SpinnerActivity.PREFERENCES_FILE, MODE_WORLD_READABLE);

        
/*
         * Get the editor for this object. The editor interface abstracts the implementation of
         * updating the SharedPreferences object.
         
*/

        SharedPreferences.Editor e 
= p.edit();

        
/*
         * Write the keys and values to the Editor
         
*/

        e.putInt(POSITION_KEY, 
this.mPos);
        e.putString(SELECTION_KEY, 
this.mSelection);

        
/*
         * Commit the changes. Return the result of the commit. The commit fails if Android
         * failed to commit the changes to persistent storage.
         
*/

        
return (e.commit());

    }

    
public int getSpinnerPosition() {
        
return this.mPos;
    }

    
public void setSpinnerPosition(int pos) {
        
this.mPos = pos;
    }

    
public String getSpinnerSelection() {
        
return this.mSelection;
    }

    
public void setSpinnerSelection(String selection) {
        
this.mSelection = selection;
    }
}

另创建测试工程:

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      
http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 
*/

package com.android.example.spinner.test;

import com.android.example.spinner.SpinnerActivity;

import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.view.KeyEvent;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;

/*
 * Tests the example application Spinner. Uses the instrumentation test class
 * ActivityInstrumentationTestCase2 as its base class. The tests include
 *   - test initial conditions
 *   - test the UI
 *   - state management - preserving state after the app is shut down and restarted, preserving
 *     state after the app is hidden (paused) and re-displayed (resumed)
 *
 * Demonstrates the use of JUnit setUp() and assert() methods.
 
*/
public class SpinnerActivityTest extends ActivityInstrumentationTestCase2<SpinnerActivity> {

    
// Number of items in the spinner's backing mLocalAdapter

    
public static final int ADAPTER_COUNT = 9;

    
// The location of Saturn in the backing mLocalAdapter array (0-based)

    
public static final int TEST_POSITION = 5;

    
// Set the initial position of the spinner to zero

    
public static final int INITIAL_POSITION = 0;

    
// The initial position corresponds to Mercury

    
public static final String INITIAL_SELECTION = "Mercury";

    
// Test values of position and selection for the testStateDestroy test

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

    
// Test values of position and selection for the testStatePause test

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

    
// The Application object for the application under test

    
private SpinnerActivity mActivity;

    
// String displayed in the spinner in the app under test

    
private String mSelection;

    
// The currently selected position in the spinner in the app under test

    
private int mPos;

    
/*
     *  The Spinner object in the app under test. Used with instrumentation to control the
     *  app under test.
     
*/

    
private Spinner mSpinner;

    
/*
     * The data backing the Spinner in the app under test.
     
*/

    
private SpinnerAdapter mPlanetData;

    
/*
     * Constructor for the test class. Required by Android test classes. The constructor
     * must call the super constructor, providing the Android package name of the app under test
     * and the Java class name of the activity in that application that handles the MAIN intent.
     
*/
    
public SpinnerActivityTest() {

        
super("com.android.example.spinner", SpinnerActivity.class);
    }

    
/*
     * Sets up the test environment before each test.
     * @see android.test.ActivityInstrumentationTestCase2#setUp()
     
*/
    @Override
    
protected void setUp() throws Exception {

        
/*
         * Call the super constructor (required by JUnit)
         
*/

        
super.setUp();

        
/*
         * prepare to send key events to the app under test by turning off touch mode.
         * Must be done before the first call to getActivity()
         
*/

        setActivityInitialTouchMode(
false);

        
/*
         * Start the app under test by starting its main activity. The test runner already knows
         * which activity this is from the call to the super constructor, as mentioned
         * previously. The tests can now use instrumentation to directly access the main
         * activity through mActivity.
         
*/
        mActivity 
= getActivity();

        
/*
         * Get references to objects in the application under test. These are
         * tested to ensure that the app under test has initialized correctly.
         
*/

        mSpinner 
= (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner01);

        mPlanetData 
= mSpinner.getAdapter();

    }

    
/*
     * Tests the initial values of key objects in the app under test, to ensure the initial
     * conditions make sense. If one of these is not initialized correctly, then subsequent
     * tests are suspect and should be ignored.
     
*/

    
public void testPreconditions() {

        
/*
         *  An example of an initialization test. Assert that the item select listener in
         *  the main Activity is not null (has been set to a valid callback)
         
*/
        assertTrue(mSpinner.getOnItemSelectedListener() 
!= null);

        
/*
         * Test that the spinner's backing mLocalAdapter was initialized correctly.
         
*/

        assertTrue(mPlanetData 
!= null);

        
/*
         *  Also ensure that the backing mLocalAdapter has the correct number of entries.
         
*/

        assertEquals(mPlanetData.getCount(), ADAPTER_COUNT);
    }

    
/*
     * Tests the UI of the main activity. Sends key events (keystrokes) to the UI, then checks
     * if the resulting spinner state is consistent with the attempted selection.
     
*/
    
public void testSpinnerUI() {

        
/*
         * Request focus for the spinner widget in the application under test,
         * and set its initial position. This code interacts with the app's View
         *  so it has to run on the app's thread not the test's thread.
         *
         * To do this, pass the necessary code to the application with
         * runOnUiThread(). The parameter is an anonymous Runnable object that
         * contains the Java statements put in it by its run() method.
         
*/
        mActivity.runOnUiThread(
            
new Runnable() {
                
public void run() {
                    mSpinner.requestFocus();
                    mSpinner.setSelection(INITIAL_POSITION);
                }
            }
        );

        
// Activate the spinner by clicking the center keypad key

        
this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);

        
// send 5 down arrow keys to the spinner

        
for (int i = 1; i <= TEST_POSITION; i++) {

            
this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
        }

        
// select the item at the current spinner position

        
this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);

        
// get the position of the selected item

        mPos 
= mSpinner.getSelectedItemPosition();

        
/*
         * from the spinner's data mLocalAdapter, get the object at the selected position
         * (this is a String value)
         
*/

        mSelection 
= (String)mSpinner.getItemAtPosition(mPos);

        
/*
         * Get the TextView widget that displays the result of selecting an item from the spinner
         
*/

        TextView resultView 
=
                (TextView) mActivity.findViewById(com.android.example.spinner.R.id.SpinnerResult);

        
// Get the String value in the EditText object

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

        
/*
         * Confirm that the EditText contains the same value as the data in the mLocalAdapter
         
*/

        assertEquals(resultText,mSelection);
    }

    
/*
     *  Tests that the activity under test maintains the spinner state when the activity halts
     *  and then restarts (for example, if the device reboots). Sets the spinner to a
     *  certain state, calls finish() on the activity, restarts the activity, and then
     *  checks that the spinner has the same state.
     *
     
*/

    
public void testStateDestroy() {

        
/*
         * Set the position and value of the spinner in the Activity. The test runner's
         * instrumentation enables this by running the test app and the main app in the same
         * process.
         
*/


        mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);

        mActivity.setSpinnerSelection(TEST_STATE_DESTROY_SELECTION);

        
// Halt the Activity by calling Activity.finish() on it

        mActivity.finish();

        
// Restart the activity by calling ActivityInstrumentationTestCase2.getActivity()

        mActivity 
= this.getActivity();

        
/*
         * Get the current position and selection from the activity.
         
*/

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

        
// test that they are the same.

        assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);

        assertEquals(TEST_STATE_DESTROY_SELECTION, currentSelection);
    }

    
/*
     * Tests that the activity under test maintains the spinner's state when the activity is
     * paused and then resumed.
     *
     * Calls the activity's onResume() method. Changes the spinner's state by
     * altering the activity's View. This means the test must run
     * on the UI Thread. All the statements in the test method may be run on
     * that thread, so instead of using the runOnUiThread() method, the
     * @UiThreadTest is used.
     
*/
    @UiThreadTest

    
public void testStatePause() {

        
/*
         * Get the instrumentation object for this application. This object
         * does all the instrumentation work for the test runner
         
*/

        Instrumentation instr 
= this.getInstrumentation();

        
/*
         * Set the activity's fields for the position and value of the spinner
         
*/

        mActivity.setSpinnerPosition(TEST_STATE_PAUSE_POSITION);

        mActivity.setSpinnerSelection(TEST_STATE_PAUSE_SELECTION);

        
/*
         *  Use the instrumentation to onPause() on the currently running Activity.
         *  This analogous to calling finish() in the testStateDestroy() method.
         *  This way demonstrates using the test class' instrumentation.
         
*/

        instr.callActivityOnPause(mActivity);

        
/*
         * Set the spinner to a test position
         
*/

        mActivity.setSpinnerPosition(
0);

        mActivity.setSpinnerSelection(
"");

        
/*
         * Call the activity's onResume() method. This forces the activity
         * to restore its state.
         
*/

        instr.callActivityOnResume(mActivity);

        
/*
         * Get the current state of the spinner
         
*/

        
int currentPosition = mActivity.getSpinnerPosition();

        String currentSelection 
= mActivity.getSpinnerSelection();

        assertEquals(TEST_STATE_PAUSE_POSITION,currentPosition);
        assertEquals(TEST_STATE_PAUSE_SELECTION,currentSelection);
  }

}
跑的时候,界面会随之变动,不过起 instrumentation确实比较慢


参考
http://www.tddtrainingcourse.co.uk/AndroidTDD.aspx
http://www.agiledata.org/essays/tdd.html

posted on 2010-10-12 09:27 都市淘沙者 阅读(1170) 评论(0)  编辑  收藏 所属分类: Android/J2ME/Symbian/Jabber


只有注册用户登录后才能发表评论。


网站导航: