JUnit是採用測試驅動開發的方式,也就是說在開發前先寫好測試代碼,主要用來講明被測試的代碼會被如何使用,錯誤處理等;而後開始寫代碼,並在測試代碼中逐步測試這些代碼,直到最後在測試代碼中徹底經過。html
以上部分與咱們日常使用IDE調試的過程是徹底同樣的,只不過是增長了測試用例管理、測試結果檢測等功能,提升了單元的效率,保證了單元測試的完整性,明確了單元測試的目標。java
一個 JUnit 測試包含如下元素:數據庫
開發代碼部分 | 測試代碼部分 | 測試工具部分 |
待測試類 A | 經過擴展 TestCase 或者構造 TestSuit 方法 編寫測試類 B |
一個測試運行器(TestRunner)R,能夠選擇圖形界面或文本界面 |
操做步驟:框架
將 B 經過命令行方式或圖形界面選擇方式傳遞給 R,R 自動運行測試,並顯示結果。ide
首先看下junit測試類庫和android中單元測試類庫:工具
SDK | 功能說明 |
junit.framework | JUnit測試框架 |
junit.runner | 實用工具類支持JUnit測試框架 |
android.test | Android 對JUnit測試框架的擴展包 |
android.test.mock | Android的一些輔助類 |
android.test.suitebuilder | 實用工具類,支持類的測試運行 |
在這些包中最爲重要的是: junit.framework、 android.test,其中前者是JUnit的核心包,後者是Andoid SDK在Junit.framework的基礎上擴展出來的包,咱們將重點解析這2個包。單元測試
junit.framework:測試
TestSuite
是測試用例的集合;TestCase與TestSuite之間的關係,有些相似於圖元對象與容器對象之間的關係
android.test包:
JUnit TestCase類
繼承自JUnit的TestCase,不能使用Instrumentation框架。但這些類包含訪問系統對象(如Context)的方法。使用Context,你能夠瀏覽資源,文件,數據庫等等。基類是AndroidTestCase,通常常見的是它的子類,和特定組件關聯。
子類有:
l ApplicationTestCase——測試整個應用程序的類。它容許你注入一個模擬的Context到應用程序中,在應用程序啓動以前初始化測試參數,並在應用程序結束以後銷燬以前檢查應用程序。
l ProviderTestCase2——測試單個ContentProvider的類。由於它要求使用MockContentResolver,並注入一個IsolatedContext,所以Provider的測試是與OS孤立的。
l ServiceTestCase——測試單個Service的類。你能夠注入一個模擬的Context或模擬的Application(或者二者),或者讓Android爲你提供Context和MockApplication。
Instrumentation TestCase類
繼承自JUnit TestCase類,並可使用Instrumentation框架,用於測試Activity。使用Instrumentation,Android能夠向程序發送事件來自動進行UI測試,並能夠精確控制Activity的啓動,監測Activity生命週期的狀態。
基類是InstrumentationTestCase。它的全部子類都能發送按鍵或觸摸事件給UI。子類還能夠注入一個模擬的Intent。
子類有:
l ActivityTestCase——Activity測試類的基類。
l SingleLaunchActivityTestCase——測試單個Activity的類。它能觸發一次setup()和tearDown(),而不是每一個方法調用時都觸發。若是你的測試方法都是針對同一個Activity的話,那就使用它吧。
l SyncBaseInstrumentation——測試Content Provider同步性的類。它使用Instrumentation在啓動測試同步性以前取消已經存在的同步對象。
l ActivityUnitTestCase——對單個Activity進行單一測試的類。使用它,你能夠注入模擬的Context或Application,或者二者。它用於對Activity進行單元測試。
不一樣於其它的Instrumentation類,這個測試類不能注入模擬的Intent。
l ActivityInstrumentationTestCase2——在正常的系統環境中測試單個Activity的類。你不能注入一個模擬的Context,但你能夠注入一個模擬的Intent。另外,你還能夠在UI線程(應用程序的主線程)運行測試方法,而且能夠給應用程序UI發送按鍵及觸摸事件。
下面找了幾個例子:
首先看junit,而後在看android test:
junit:
待測試類A:
package cn.edu.wtu.junit; public class Calcuator { public double add(double n1, double n2) { return n1 + n1; } }
測試代碼B:擴展testcase
package cn.edu.wtu.junit; import junit.framework.TestCase; public class TestCalcuator extends TestCase { public void testAdd(){ Calcuator calcuator=new Calcuator(); double result=calcuator.add(1,2); assertEquals(3,result,0); } }
testsuit 測試容器: package cn.edu.wtu.junit; import junit.framework.Test; import junit.framework.TestSuite; import junit.textui.TestRunner; public class TestAll extends TestSuite { public static Test suite() { TestSuite suite = new TestSuite("TestSuite Test"); suite.addTestSuite(TestCalcuator.class); suite.addTestSuite(TestCalcuator2.class); return suite; } public static void main(String args[]){ // 命令行輸出 測試工具 一個測試運行器 TestRunner.run(suite()); } }
run on junit 圖形界面顯示:
run java application:控制檯輸出:
android test :
首先看下非instrumentation框架測試:
測試代碼:
package aexp.junit; import android.test.AndroidTestCase; import android.util.Log; public class MathTest extends AndroidTestCase { protected int i1; protected int i2; static final String LOG_TAG = "MathTest"; public void setUp() { i1 = 2; i2 = 3; } public void testAdd() { Log.d( LOG_TAG, "testAdd" ); assertTrue( LOG_TAG+"1", ( ( i1 + i2 ) == 5 ) ); } public void testAndroidTestCaseSetupProperly() { super.testAndroidTestCaseSetupProperly(); Log.d( LOG_TAG, "testAndroidTestCaseSetupProperly" ); } }
package aexp.junit; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.provider.Contacts; import android.util.Log; import android.test.AndroidTestCase; public class ContactTest extends AndroidTestCase { static final String LOG_TAG = "ContactTest"; static final String TESTUSER_NAME = "Test User"; static final String TESTUSER_NOTES = "Test note"; ContentResolver contentResolver; Uri newPerson; public void setUp() { contentResolver = getContext().getContentResolver(); ContentValues person = new ContentValues(); person.put(Contacts.People.NAME, TESTUSER_NAME ); person.put(Contacts.People.NOTES, TESTUSER_NOTES ); newPerson = contentResolver.insert( Contacts.People.CONTENT_URI,person); } public void testInsertContact() { Log.d( LOG_TAG, "testInsertContact" ); assertNotNull( newPerson ); } public void testQueryContact() { Log.d( LOG_TAG, "testQueryContact" ); String columns[] = { Contacts.People.NAME, Contacts.People.NOTES }; Cursor c = contentResolver.query( Contacts.People.CONTENT_URI, columns, Contacts.People.NAME+"=?", new String[] { TESTUSER_NAME }, null ); assertNotNull( c ); int hits = 0; while( c.moveToNext() ) { int nameColumnIndex = c.getColumnIndex( Contacts.People.NAME ); int notesColumnIndex = c.getColumnIndex( Contacts.People.NOTES ); String name = c.getString( nameColumnIndex ); String notes = c.getString( notesColumnIndex ); Log.d( LOG_TAG,"retrieved name: "+name ); Log.d( LOG_TAG,"retrieved notes: "+notes ); assertEquals( TESTUSER_NAME, name ); assertEquals( TESTUSER_NOTES, notes ); ++hits; } assertEquals( hits,1 ); c.close(); } public void tearDown() { contentResolver.delete( newPerson, null, null ); } }
子樹:
package aexp.junit; import junit.framework.TestSuite; import junit.framework.Assert; public class SomeTest extends TestSuite { public void testSomething() throws Throwable { Assert.assertTrue(1 + 1 == 2); } public void testSomethingElse() throws Throwable { Assert.assertTrue(1 + 1 == 3); } }
測試樹:
package aexp.junit; import junit.framework.TestSuite; public class ExampleSuite extends TestSuite { public ExampleSuite() { addTestSuite( MathTest.class ); addTestSuite( ContactTest.class ); addTestSuite(SomeTest.class); } }
測試運行器:
package aexp.junit; import android.app.Activity; import android.os.Bundle; import android.test.AndroidTestCase; import android.test.AndroidTestRunner; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import android.view.View; import android.util.Log; import junit.framework.TestListener; import junit.framework.Test; import junit.framework.AssertionFailedError; public class JUnit extends Activity { static final String LOG_TAG = "junit"; Thread testRunnerThread = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button launcherButton = (Button)findViewById( R.id.launch_button ); launcherButton.setOnClickListener( new View.OnClickListener() { public void onClick( View view ) { startTest(); } } ); } private synchronized void startTest() { if( ( testRunnerThread != null ) && !testRunnerThread.isAlive() ) testRunnerThread = null; if( testRunnerThread == null ) { testRunnerThread = new Thread( new TestRunner( this ) ); testRunnerThread.start(); } else Toast.makeText( this, "Test is still running", Toast.LENGTH_SHORT).show(); } } //顯示線程 class TestDisplay implements Runnable { public enum displayEvent{START_TEST,END_TEST,ERROR,FAILURE,} displayEvent ev; String testName; int testCounter; int errorCounter; int failureCounter; TextView statusText; TextView testCounterText; TextView errorCounterText; TextView failureCounterText; public TestDisplay( displayEvent ev, String testName, int testCounter, int errorCounter, int failureCounter, TextView statusText, TextView testCounterText, TextView errorCounterText, TextView failureCounterText ) { this.ev = ev; this.testName = testName; this.testCounter = testCounter; this.errorCounter = errorCounter; this.failureCounter = failureCounter; this.statusText = statusText; this.testCounterText = testCounterText; this.errorCounterText = errorCounterText; this.failureCounterText = failureCounterText; } public void run() { StringBuffer status = new StringBuffer(); switch( ev ) { case START_TEST: status.append( "Starting" ); break; case END_TEST: status.append( "Ending" ); break; case ERROR: status.append( "Error: " ); break; case FAILURE: status.append( "Failure: " ); break; } status.append( ": " ); status.append( testName ); statusText.setText( new String( status ) ); testCounterText.setText( "Tests: "+testCounter ); errorCounterText.setText( "Errors: "+errorCounter ); failureCounterText.setText( "Failure: "+failureCounter ); } } class TestRunner implements Runnable,TestListener { static final String LOG_TAG = "TestRunner"; int testCounter; int errorCounter; int failureCounter; TextView statusText; TextView testCounterText; TextView errorCounterText; TextView failureCounterText; Activity parentActivity; public TestRunner( Activity parentActivity ) { this.parentActivity = parentActivity; } public void run() { testCounter = 0; errorCounter = 0; failureCounter = 0; statusText = (TextView)parentActivity. findViewById( R.id.status ); testCounterText = (TextView)parentActivity. findViewById( R.id.testCounter ); errorCounterText = (TextView)parentActivity. findViewById( R.id.errorCounter ); failureCounterText = (TextView)parentActivity. findViewById( R.id.failureCounter ); Log.d( LOG_TAG, "Test started" ); // 測試運行器 AndroidTestRunner testRunner = new AndroidTestRunner(); testRunner.setTest( new ExampleSuite() ); testRunner.addTestListener( this ); testRunner.setContext( parentActivity ); testRunner.runTest(); Log.d( LOG_TAG, "Test ended" ); } // TestListener public void addError(Test test, Throwable t) { Log.d( LOG_TAG, "addError: "+test.getClass().getName() ); Log.d( LOG_TAG, t.getMessage(), t ); ++errorCounter; TestDisplay td = new TestDisplay( TestDisplay.displayEvent.ERROR, test.getClass().getName(), testCounter, errorCounter, failureCounter, statusText, testCounterText, errorCounterText, failureCounterText ); parentActivity.runOnUiThread( td ); } public void addFailure(Test test, AssertionFailedError t) { Log.d( LOG_TAG, "addFailure: "+test.getClass().getName() ); Log.d( LOG_TAG, t.getMessage(), t ); ++failureCounter; TestDisplay td = new TestDisplay( TestDisplay.displayEvent.FAILURE, test.getClass().getName(), testCounter, errorCounter, failureCounter, statusText, testCounterText, errorCounterText, failureCounterText ); parentActivity.runOnUiThread( td ); } public void endTest(Test test) { Log.d( LOG_TAG, "endTest: "+test.getClass().getName() ); TestDisplay td = new TestDisplay( TestDisplay.displayEvent.END_TEST, test.getClass().getName(), testCounter, errorCounter, failureCounter, statusText, testCounterText, errorCounterText, failureCounterText ); parentActivity.runOnUiThread( td ); } public void startTest(Test test) { Log.d( LOG_TAG, "startTest: "+test.getClass().getName() ); ++testCounter; TestDisplay td = new TestDisplay( TestDisplay.displayEvent.START_TEST, test.getClass().getName(), testCounter, errorCounter, failureCounter, statusText, testCounterText, errorCounterText, failureCounterText ); parentActivity.runOnUiThread( td ); } }
運行結果:
instrumentation框架:
首先新建一個android工程:
裏面編寫3個activity:MainActivity,HomeActivity,LoginActivity
MainActivity是加載界面,LoginActivity是登錄界面,HomeActivity是最終界面
package cn.edu.wtu.junit; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; public class MainActivity extends Activity { private static final boolean DEBUG = true; private static final String TAG = "-- MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { if (DEBUG) { Log.i(TAG, "onCreate"); } super.onCreate(savedInstanceState); setContentView(R.layout.act_main); View toLoginView = findViewById(R.id.to_login); toLoginView.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { if (DEBUG) { Log.i(TAG, "toLoginView clicked"); } Intent intent = new Intent(getApplicationContext(), LoginActivity.class); startActivity(intent); } }); } }
package cn.edu.wtu.junit; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.EditText; public class LoginActivity extends Activity { private static final boolean DEBUG = true; private static final String TAG = "-- LoginActivity"; private EditText mUsernameView; private EditText mPasswordView; @Override protected void onCreate(Bundle savedInstanceState) { if (DEBUG) { Log.i(TAG, "onCreate"); } super.onCreate(savedInstanceState); setContentView(R.layout.act_login); mUsernameView = (EditText) findViewById(R.id.username); mPasswordView = (EditText) findViewById(R.id.password); View submitView = findViewById(R.id.submit); submitView.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { if (DEBUG) { Log.i(TAG, "submitView clicked"); } Intent intent = new Intent(getApplicationContext(), HomeActivity.class); intent.putExtra(HomeActivity.EXTRA_USERNAME, mUsernameView.getText().toString()); intent.putExtra(HomeActivity.EXTRA_PASSWORD, mPasswordView.getText().toString()); startActivity(intent); } }); View resetView = findViewById(R.id.reset); resetView.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { if (DEBUG) { Log.i(TAG, "resetView clicked"); } mUsernameView.setText(""); mPasswordView.setText(""); mUsernameView.requestFocus(); } }); } }
package cn.edu.wtu.junit; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.widget.TextView; public class HomeActivity extends Activity { private static final boolean DEBUG = true; private static final String TAG = "-- HomeActivity"; public static final String EXTRA_USERNAME = "yuan.activity.username"; public static final String EXTRA_PASSWORD = "yuan.activity.password"; @Override protected void onCreate(Bundle savedInstanceState) { if (DEBUG) { Log.i(TAG, "onCreate"); } super.onCreate(savedInstanceState); Intent intent = getIntent(); StringBuilder sb = new StringBuilder(); sb.append("username:").append(intent.getStringExtra(EXTRA_USERNAME)).append("\n"); sb.append("password:").append(intent.getStringExtra(EXTRA_PASSWORD)); setContentView(R.layout.act_home); TextView loginContentView = (TextView) findViewById(R.id.login_content); loginContentView.setText(sb.toString()); } }
而後新建一個測試工程,基於上面一個project:
package cn.edu.wtu.test; import android.app.Instrumentation; import android.test.ActivityInstrumentationTestCase2; import android.test.UiThreadTest; import android.test.suitebuilder.annotation.Suppress; import android.util.Log; import android.view.View; import cn.edu.wtu.junit.MainActivity; public class MainActivityTest extends ActivityInstrumentationTestCase2 { private static final String TAG = "=== MainActivityTest"; private Instrumentation mInstrument; private MainActivity mActivity; private View mToLoginView; public MainActivityTest() { super("cn.edu.wtu.junit", MainActivity.class); } @Override public void setUp() throws Exception { super.setUp(); mInstrument = getInstrumentation(); // 啓動被測試的Activity mActivity = getActivity(); mToLoginView = mActivity.findViewById(cn.edu.wtu.junit.R.id.to_login); } public void testPreConditions() { // 在執行測試以前,確保程序的重要對象已被初始化 assertTrue(mToLoginView != null); } // @UiThreadTest // 這將會在UI線程裏運行方法裏全部的語句。不與UI交互的方法不容許這麼作 // 注意:waitForIdleSync和sendKeys不容許在UI線程裏運行 public void testToLogin() { // @UiThreadTest註解使整個方法在UI線程上執行,等同於上面註解掉的代碼 // exception mInstrument.runOnMainSync(new Runnable() { public void run() { mToLoginView.requestFocus(); mToLoginView.performClick(); } }); // mActivity.runOnUiThread(new Runnable(){ // // @Override // public void run() { // // mToLoginView.requestFocus(); // mToLoginView.performClick(); // } // // }); } @Suppress public void testNotCalled() { // 使用了@Suppress註解的方法不會被測試 Log.i(TAG, "method 'testNotCalled' is called"); } @Override public void tearDown() throws Exception { super.tearDown(); } }
package cn.edu.wtu.test; import android.app.Instrumentation; import android.test.ActivityInstrumentationTestCase2; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.widget.EditText; import cn.edu.wtu.junit.LoginActivity; public class LoginActivityTest extends ActivityInstrumentationTestCase2 { private static final String TAG = "=== LoginActivityTest"; private Instrumentation mInstrument; private LoginActivity mActivity; private EditText mUsernameView; private EditText mPasswordView; private View mSubmitView; private View mResetView; public LoginActivityTest() { super("cn.edu.wtu.junit", LoginActivity.class); } @Override public void setUp() throws Exception { super.setUp(); /* * 關閉觸屏模式爲了控制從測試程序中發送給模擬器或設備的按鍵事件,你必須關閉觸屏模式。若是你不這麼作,按鍵事件將被忽略。 * 你須要在調用getActivity()啓動Activity以前調用ActivityInstrumentationTestCase2.setActivityTouchMode(false)。 * 你必須在非UI線程中運行這個調用。基於這個緣由,你不能在聲明有@UIThread的測試方法調用。能夠在setUp()中調用。 * 要向程序發送key事件的話,必須在getActivity以前調用該方法來關閉touch模式 * 不然key事件會被忽略 */ setActivityInitialTouchMode(false); mInstrument = getInstrumentation(); mActivity = getActivity(); Log.i(TAG, "current activity: " + mActivity.getClass().getName()); mUsernameView = (EditText) mActivity.findViewById(cn.edu.wtu.junit.R.id.username); mPasswordView = (EditText) mActivity.findViewById(cn.edu.wtu.junit.R.id.password); mSubmitView = mActivity.findViewById(cn.edu.wtu.junit.R.id.submit); mResetView = mActivity.findViewById(cn.edu.wtu.junit.R.id.reset); } public void testPreConditions() { assertTrue(mUsernameView != null); assertTrue(mPasswordView != null); assertTrue(mSubmitView != null); assertTrue(mResetView != null); } public void testInput() { input(); assertEquals("yuan", mUsernameView.getText().toString()); assertEquals("1123", mPasswordView.getText().toString()); } public void testSubmit() { input(); mInstrument.runOnMainSync(new Runnable() { public void run() { mSubmitView.requestFocus(); mSubmitView.performClick(); } }); } public void testReset() { input(); mInstrument.runOnMainSync(new Runnable() { public void run() { mResetView.requestFocus(); mResetView.performClick(); } }); assertEquals("", mUsernameView.getText().toString()); assertEquals("", mPasswordView.getText().toString()); } @Override public void tearDown() throws Exception { super.tearDown(); } private void input() { mActivity.runOnUiThread(new Runnable() { public void run() { mUsernameView.requestFocus(); } }); // 由於測試用例運行在單獨的線程上,這裏最好要 // 同步application,等待其執行完後再運行 mInstrument.waitForIdleSync(); sendKeys(KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U, KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_N); // 效果同上面sendKeys以前的代碼 mInstrument.runOnMainSync(new Runnable() { public void run() { mPasswordView.requestFocus(); } }); sendKeys(KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3); } }
package cn.edu.wtu.test; import android.content.Intent; import android.test.ActivityUnitTestCase; import android.widget.TextView; import cn.edu.wtu.junit.HomeActivity; public class HomeActivityTest extends ActivityUnitTestCase { private static final String TAG = "=== HomeActivityTest"; private static final String LOGIN_CONTENT = "username:yuan\npassword:1123"; private HomeActivity mHomeActivity; private TextView mLoginContentView; public HomeActivityTest() { super(HomeActivity.class); } @Override public void setUp() throws Exception { super.setUp(); Intent intent = new Intent(); intent.putExtra(HomeActivity.EXTRA_USERNAME, "yuan"); intent.putExtra(HomeActivity.EXTRA_PASSWORD, "1123"); // HomeActivity有extra參數,因此咱們須要以intent來啓動它 mHomeActivity = launchActivityWithIntent("cn.edu.wtu.junit", HomeActivity.class, intent); mLoginContentView = (TextView) mHomeActivity.findViewById(cn.edu.wtu.junit.R.id.login_content); } public void testLoginContent() { assertEquals(LOGIN_CONTENT, mLoginContentView.getText().toString()); } @Override public void tearDown() throws Exception { super.tearDown(); } }
run on android unit:
參考:
http://www.ibm.com/developerworks/cn/java/j-lo-junit-src/
http://www.moandroid.com/?page_id=1176
http://www.ibm.com/developerworks/cn/java/j-lo-junit4/
http://www.cnblogs.com/xirihanlin/archive/2010/06/15/1758677.html
轉:http://www.open-open.com/lib/view/open1328152424546.html