通过JUnit进行Android单元测试

来源:互联网 发布:幻萌网络微博 编辑:程序博客网 时间:2024/06/05 18:07


要了解android单元测试,首先必须了解junit

什么是 JUnit ?

JUnit是采用测试驱动开发的方式,也就是说在开发前先写好测试代码,主要用来说明被测试的代码会被如何使用,错误处理等;然后开始写代码,并在测试代码中逐步测试这些代码,直到最后在测试代码中完全通过。

现简要说JUnit的4大功能

  1. 管理测试用例。修改了哪些代码,这些代码的修改会对哪些部分有影响,通过JUnit将这次的修改做个完整测试。这也就JUnit中所谓的TestSuite。
  2. 定义测试代码。这也就是JUnit中所谓的TestCase,根据源代码的测试需要定义每个TestCase,并将TestCase添加到相应的TestSuite方便管理。
  3. 定义测试环境。在TestCase测试前会先调用“环境”配置,在测试中使用,当然也可以在测试用例中直接定义测试环境。
  4. 检测测试结果。对于每种正常、异常情况下的测试,运行结果是什么、结果是否是我们预期的等都需要有个明确的定义,JUnit在这方面提供了强大的功能。

以上部分与我们平常使用IDE调试的过程是完全一样的,只不过是增加了测试用例管理、测试结果检测等功能,提高了单元的效率,保证了单元测试的完整性,明确了单元测试的目标。

JUnit 基本原理

一个 JUnit 测试包含以下元素:

表 1. 测试用例组成

开发代码部分测试代码部分测试工具部分待测试类 A通过扩展 TestCase 或者构造 TestSuit 方法
编写测试类 B一个测试运行器(TestRunner)R,可以选择图形界面或文本界面

操作步骤:

将 B 通过命令行方式或图形界面选择方式传递给 R,R 自动运行测试,并显示结果。

首先看下junit测试类库和android中单元测试类库:

 

SDK功能说明junit.frameworkJUnit测试框架junit.runner实用工具类支持JUnit测试框架android.testAndroid 对JUnit测试框架的扩展包android.test.mockAndroid的一些辅助类android.test.suitebuilder实用工具类,支持类的测试运行在这些包中最为重要的是: junit.framework、 android.test,其中前者是JUnit的核心包,后者是Andoid SDK在Junit.framework的基础上扩展出来的包,我们将重点解析这2个包。

junit.framework:


 

  1. TestSuit:TestSuite是测试用例的集合;
  2. TestCase:定义运行多个测试用例;
  3. TestResult:收集一个测试案例的结果,测试结果分为失败和错误,如果未能预计的断言就是失败,错误就像一个ArrayIndexOutOfBoundsException异常而导致的无法预料的问题;
  4. TestFailure:测试失败时捕获的异常;
  5. Assert:断言的方法集,当断言失败时显示信息;
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:

?
1
2
3
4
5
6
7
packagecn.edu.wtu.junit;
 
publicclass Calcuator {
    publicdouble add(doublen1, doublen2) {
        returnn1 + n1;
    }
}

测试代码B:扩展testcase

?
1
2
3
4
5
6
7
8
9
10
11
12
packagecn.edu.wtu.junit;
 
importjunit.framework.TestCase;
 
publicclass TestCalcuator extendsTestCase {
    publicvoid testAdd(){
        Calcuator calcuator=newCalcuator();
        doubleresult=calcuator.add(1,2);
        assertEquals(3,result,0);
    }
 
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
testsuit 测试容器:
packagecn.edu.wtu.junit;
 
importjunit.framework.Test;
importjunit.framework.TestSuite;
importjunit.textui.TestRunner;
 
publicclass TestAll extendsTestSuite {
    publicstatic Test suite() {
        TestSuite suite = newTestSuite("TestSuite Test");
        suite.addTestSuite(TestCalcuator.class);
        suite.addTestSuite(TestCalcuator2.class);
        returnsuite;
    }
    publicstatic void main(String args[]){
//      命令行输出 测试工具 一个测试运行器
        TestRunner.run(suite());
    }
}

run on junit 图形界面显示:

通过JUnit进行Android单元测试

run java application:控制台输出:

通过JUnit进行Android单元测试

android test :

首先看下非instrumentation框架测试:

测试代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
packageaexp.junit;
 
importandroid.test.AndroidTestCase;
importandroid.util.Log;
 
publicclass MathTest extendsAndroidTestCase
{
    protectedint i1;
    protectedint i2;
    staticfinal String LOG_TAG = "MathTest";
 
    publicvoid setUp()
    {
        i1 = 2;
        i2 = 3;   
    }
 
    publicvoid testAdd()
    {
        Log.d( LOG_TAG, "testAdd");
        assertTrue( LOG_TAG+"1", ( ( i1 + i2 ) == 5) );
    }
 
    publicvoid testAndroidTestCaseSetupProperly()
    {
        super.testAndroidTestCaseSetupProperly();
        Log.d( LOG_TAG, "testAndroidTestCaseSetupProperly");
    }
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
packageaexp.junit;
 
importandroid.content.ContentResolver;
importandroid.content.ContentValues;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.provider.Contacts;
importandroid.util.Log;
importandroid.test.AndroidTestCase;
 
publicclass ContactTest extendsAndroidTestCase
{
    staticfinal String LOG_TAG = "ContactTest";
    staticfinal String TESTUSER_NAME = "Test User";
    staticfinal String TESTUSER_NOTES = "Test note";
    ContentResolver contentResolver;
    Uri newPerson;
 
    publicvoid setUp()
    {
        contentResolver = getContext().getContentResolver();
        ContentValues person = newContentValues();
        person.put(Contacts.People.NAME, TESTUSER_NAME );
        person.put(Contacts.People.NOTES, TESTUSER_NOTES );
 
        newPerson = contentResolver.insert(
                    Contacts.People.CONTENT_URI,person);
    }
 
    publicvoid testInsertContact()
    {
        Log.d( LOG_TAG, "testInsertContact");
        assertNotNull( newPerson );
    }
 
    publicvoid 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+"=?",
                    newString[] { TESTUSER_NAME },
                    null);
        assertNotNull( c );
        inthits = 0;
        while( c.moveToNext() )
        {
            intnameColumnIndex = c.getColumnIndex( Contacts.People.NAME );
            intnotesColumnIndex = 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();
    }
 
    publicvoid tearDown()
    {
        contentResolver.delete( newPerson, nullnull);
    }
}

子树:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
packageaexp.junit;
 
importjunit.framework.TestSuite;
importjunit.framework.Assert;
 
publicclass SomeTest extendsTestSuite {
    publicvoid testSomething() throwsThrowable
    {
           Assert.assertTrue(11== 2);
    }
 
    publicvoid testSomethingElse() throwsThrowable
    {
           Assert.assertTrue(11== 3);
    }
}

测试树:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
packageaexp.junit;
 
importjunit.framework.TestSuite;
 
publicclass ExampleSuite extendsTestSuite
{
    publicExampleSuite()
    {
        addTestSuite( MathTest.class);
        addTestSuite( ContactTest.class);
        addTestSuite(SomeTest.class);
    }
}

测试运行器:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
packageaexp.junit;
 
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.test.AndroidTestCase;
importandroid.test.AndroidTestRunner;
importandroid.widget.Button;
importandroid.widget.TextView;
importandroid.widget.Toast;
importandroid.view.View;
importandroid.util.Log;
importjunit.framework.TestListener;
importjunit.framework.Test;
importjunit.framework.AssertionFailedError;
 
publicclass JUnit extendsActivity {
    staticfinal String LOG_TAG = "junit";
    Thread testRunnerThread = null;
 
    /** Called when the activity is first created. */
    @Override
    publicvoid onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button launcherButton = (Button)findViewById( R.id.launch_button );
        launcherButton.setOnClickListener( newView.OnClickListener() {
            publicvoid onClick( View view ) {
                startTest();
            }
        } );
    }
 
    privatesynchronized void startTest()
    {
        if( ( testRunnerThread != null) &&
            !testRunnerThread.isAlive() )
            testRunnerThread = null;
        if( testRunnerThread == null) {
            testRunnerThread = newThread( newTestRunner( this) );
            testRunnerThread.start();
        else
            Toast.makeText(
                        this,
                        "Test is still running",
                        Toast.LENGTH_SHORT).show();
    }
 
}
 
    //显示线程
    classTestDisplay implementsRunnable
    {
            publicenum displayEvent{START_TEST,END_TEST,ERROR,FAILURE,}
            displayEvent ev;
            String testName;
            inttestCounter;
            interrorCounter;
            intfailureCounter;
            TextView statusText;
            TextView testCounterText;
            TextView errorCounterText;
            TextView failureCounterText;
     
            publicTestDisplay( displayEvent ev,
                            String testName,
                            inttestCounter,
                            interrorCounter,
                            intfailureCounter,
                            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;
            }
     
            publicvoid run()
            {
                StringBuffer status = newStringBuffer();
                switch( ev ) {
                    caseSTART_TEST:
                        status.append( "Starting");
                    break;
     
                caseEND_TEST:
                    status.append( "Ending");
                    break;
     
                caseERROR:
                    status.append( "Error: " );
                    break;
     
                caseFAILURE:
                    status.append( "Failure: " );
                    break;
                 
            }
            status.append( ": " );
            status.append( testName );
            statusText.setText( newString( status ) );
            testCounterText.setText( "Tests: "+testCounter );
            errorCounterText.setText( "Errors: "+errorCounter );
            failureCounterText.setText( "Failure: "+failureCounter );
            }
    }
 
    classTestRunner implementsRunnable,TestListener 
    {
            staticfinal String LOG_TAG = "TestRunner";
            inttestCounter;
            interrorCounter;
            intfailureCounter;
            TextView statusText;
            TextView testCounterText;
            TextView errorCounterText;
            TextView failureCounterText;
            Activity parentActivity;
     
            publicTestRunner( Activity parentActivity )
            {
                this.parentActivity = parentActivity;
            }
     
            publicvoid 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 = newAndroidTestRunner();
                testRunner.setTest( newExampleSuite() );
                testRunner.addTestListener( this);
                testRunner.setContext( parentActivity );
                testRunner.runTest();
                Log.d( LOG_TAG, "Test ended" );
            }
     
    // TestListener
            publicvoid addError(Test test, Throwable t)
            {
                Log.d( LOG_TAG, "addError: "+test.getClass().getName() );
                Log.d( LOG_TAG, t.getMessage(), t );
                ++errorCounter;
                TestDisplay td = newTestDisplay(
                        TestDisplay.displayEvent.ERROR,
                        test.getClass().getName(),
                        testCounter,
                        errorCounter,
                        failureCounter,
                        statusText,
                        testCounterText,
                        errorCounterText,
                        failureCounterText );
                parentActivity.runOnUiThread( td );
            }
     
            publicvoid addFailure(Test test, AssertionFailedError t)
            {
                Log.d( LOG_TAG, "addFailure: "+test.getClass().getName() );
                Log.d( LOG_TAG, t.getMessage(), t );
                ++failureCounter;
                TestDisplay td = newTestDisplay(
                        TestDisplay.displayEvent.FAILURE,
                        test.getClass().getName(),
                        testCounter,
                        errorCounter,
                        failureCounter,
                        statusText,
                        testCounterText,
                        errorCounterText,
                        failureCounterText );
                parentActivity.runOnUiThread( td );
            }
     
            publicvoid endTest(Test test)
            {
                Log.d( LOG_TAG, "endTest: "+test.getClass().getName() );
                TestDisplay td = newTestDisplay(
                        TestDisplay.displayEvent.END_TEST,
                        test.getClass().getName(),
                        testCounter,
                        errorCounter,
                        failureCounter,
                        statusText,
                        testCounterText,
                        errorCounterText,
                        failureCounterText );
                parentActivity.runOnUiThread( td );
            }
     
            publicvoid startTest(Test test)
            {
                Log.d( LOG_TAG, "startTest: "+test.getClass().getName() );
                ++testCounter;
                TestDisplay td = newTestDisplay(
                        TestDisplay.displayEvent.START_TEST,
                        test.getClass().getName(),
                        testCounter,
                        errorCounter,
                        failureCounter,
                        statusText,
                        testCounterText,
                        errorCounterText,
                        failureCounterText );
                parentActivity.runOnUiThread( td );
            }
}
运行结果:

通过JUnit进行Android单元测试

 

instrumentation框架:

首先新建一个android工程:

里面编写3个activity:MainActivity,HomeActivity,LoginActivity

MainActivity是加载界面,LoginActivity是登陆界面,HomeActivity是最终界面

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
packagecn.edu.wtu.junit;
 
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.View;
 
publicclass MainActivity extendsActivity {
     
    privatestatic final boolean DEBUG = true;
    privatestatic final String TAG = "-- MainActivity";
 
    @Override
    protectedvoid 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(newView.OnClickListener() {
            publicvoid onClick(View view) {
                if(DEBUG) {
                    Log.i(TAG, "toLoginView clicked");
                }
 
                Intent intent = newIntent(getApplicationContext(), LoginActivity.class);
                startActivity(intent);
            }
        });
    }
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
packagecn.edu.wtu.junit;
 
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.View;
importandroid.widget.EditText;
 
publicclass LoginActivity extendsActivity {
    privatestatic final boolean DEBUG = true;
    privatestatic final String TAG = "-- LoginActivity";
 
    privateEditText mUsernameView;
    privateEditText mPasswordView;
 
    @Override
    protectedvoid 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(newView.OnClickListener() {
            publicvoid onClick(View view) {
                if(DEBUG) {
                    Log.i(TAG, "submitView clicked");
                }
 
                Intent intent = newIntent(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(newView.OnClickListener() {
            publicvoid onClick(View view) {
                if(DEBUG) {
                    Log.i(TAG, "resetView clicked");
                }
 
                mUsernameView.setText("");
                mPasswordView.setText("");
                mUsernameView.requestFocus();
            }
        });
    }
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
packagecn.edu.wtu.junit;
 
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.widget.TextView;
 
publicclass HomeActivity extendsActivity {
    privatestatic final boolean DEBUG = true;
    privatestatic final String TAG = "-- HomeActivity";
 
    publicstatic final String EXTRA_USERNAME = "yuan.activity.username";
    publicstatic final String EXTRA_PASSWORD = "yuan.activity.password";
 
    @Override
    protectedvoid onCreate(Bundle savedInstanceState) {
        if(DEBUG) {
            Log.i(TAG, "onCreate");
        }
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        StringBuilder sb = newStringBuilder();
        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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
packagecn.edu.wtu.test;
 
importandroid.app.Instrumentation;
importandroid.test.ActivityInstrumentationTestCase2;
importandroid.test.UiThreadTest;
importandroid.test.suitebuilder.annotation.Suppress;
importandroid.util.Log;
importandroid.view.View;
importcn.edu.wtu.junit.MainActivity;
 
publicclass MainActivityTest extendsActivityInstrumentationTestCase2
    
    
    
    
   <mainactivity>
     
     
     
     
     {
    privatestatic final String TAG = "=== MainActivityTest";
 
    privateInstrumentation mInstrument;
    privateMainActivity mActivity;
    privateView mToLoginView;
 
    publicMainActivityTest() {
        super("cn.edu.wtu.junit", MainActivity.class);
    }
 
    @Override
    publicvoid setUp() throwsException {
        super.setUp();
        mInstrument = getInstrumentation();
        // 启动被测试的Activity
        mActivity = getActivity();
        mToLoginView = mActivity.findViewById(cn.edu.wtu.junit.R.id.to_login);
    }
 
    publicvoid testPreConditions() {
        // 在执行测试之前,确保程序的重要对象已被初始化
        assertTrue(mToLoginView != null);
    }
 
 
     
//  @UiThreadTest
//  这将会在UI线程里运行方法里所有的语句。不与UI交互的方法不允许这么做
//  注意:waitForIdleSync和sendKeys不允许在UI线程里运行
    publicvoid testToLogin() {
        // @UiThreadTest注解使整个方法在UI线程上执行,等同于上面注解掉的代码
 
//      exception
        mInstrument.runOnMainSync(newRunnable() {
            publicvoid run() {
                mToLoginView.requestFocus();
                mToLoginView.performClick();
            }
        });
         
//      mActivity.runOnUiThread(new Runnable(){
//
//          @Override
//          public void run() {
//             
//              mToLoginView.requestFocus();
//              mToLoginView.performClick();
//          }
//         
//      });
    }
 
    @Suppress
    publicvoid testNotCalled() {
        // 使用了@Suppress注解的方法不会被测试
        Log.i(TAG, "method 'testNotCalled' is called");
    }
 
    @Override
    publicvoid tearDown() throwsException {
        super.tearDown();
    }
}
    
    
    
    
   </mainactivity>

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
packagecn.edu.wtu.test;
 
importandroid.app.Instrumentation;
importandroid.test.ActivityInstrumentationTestCase2;
importandroid.util.Log;
importandroid.view.KeyEvent;
importandroid.view.View;
importandroid.widget.EditText;
importcn.edu.wtu.junit.LoginActivity;
 
publicclass LoginActivityTest extendsActivityInstrumentationTestCase2
    
    
    
    
   <loginactivity>
     
     
     
     
     {
    privatestatic final String TAG = "=== LoginActivityTest";
 
    privateInstrumentation mInstrument;
    privateLoginActivity mActivity;
    privateEditText mUsernameView;
    privateEditText mPasswordView;
    privateView mSubmitView;
    privateView mResetView;
 
    publicLoginActivityTest() {
        super("cn.edu.wtu.junit", LoginActivity.class);
    }
 
    @Override
    publicvoid setUp() throwsException {
        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);
    }
 
    publicvoid testPreConditions() {
        assertTrue(mUsernameView != null);
        assertTrue(mPasswordView != null);
        assertTrue(mSubmitView != null);
        assertTrue(mResetView != null);
    }
 
    publicvoid testInput() {
        input();
        assertEquals("yuan", mUsernameView.getText().toString());
        assertEquals("1123", mPasswordView.getText().toString());
    }
 
    publicvoid testSubmit() {
        input();
        mInstrument.runOnMainSync(newRunnable() {
            publicvoid run() {
                mSubmitView.requestFocus();
                mSubmitView.performClick();
            }
        });
    }
 
    publicvoid testReset() {
        input();
        mInstrument.runOnMainSync(newRunnable() {
            publicvoid run() {
                mResetView.requestFocus();
                mResetView.performClick();
            }
        });
        assertEquals("", mUsernameView.getText().toString());
        assertEquals("", mPasswordView.getText().toString());
    }
 
    @Override
    publicvoid tearDown() throwsException {
        super.tearDown();
    }
 
    privatevoid input() {
        mActivity.runOnUiThread(newRunnable() {
            publicvoid run() {
                mUsernameView.requestFocus();
            }
        });
        // 因为测试用例运行在单独的线程上,这里最好要
        // 同步application,等待其执行完后再运行
        mInstrument.waitForIdleSync();
        sendKeys(KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U,
                KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_N);
 
        // 效果同上面sendKeys之前的代码
        mInstrument.runOnMainSync(newRunnable() {
            publicvoid run() {
                mPasswordView.requestFocus();
            }
        });
        sendKeys(KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_1,
                KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3);
    }
}
    
    
    
    
   </loginactivity>

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
packagecn.edu.wtu.test;
 
importandroid.content.Intent;
importandroid.test.ActivityUnitTestCase;
importandroid.widget.TextView;
importcn.edu.wtu.junit.HomeActivity;
 
publicclass HomeActivityTest extendsActivityUnitTestCase
    
    
    
    
   <homeactivity>
     
     
     
     
     {
    privatestatic final String TAG = "=== HomeActivityTest";
 
    privatestatic final String LOGIN_CONTENT = "username:yuan\npassword:1123";
 
    privateHomeActivity mHomeActivity;
    privateTextView mLoginContentView;
 
    publicHomeActivityTest() {
        super(HomeActivity.class);
    }
 
    @Override
    publicvoid setUp() throwsException {
        super.setUp();
        Intent intent = newIntent();
        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);
    }
 
    publicvoid testLoginContent() {
        assertEquals(LOGIN_CONTENT, mLoginContentView.getText().toString());
    }
 
    @Override
    publicvoid tearDown() throwsException {
        super.tearDown();
    }
}
    
    
    
    
   </homeactivity>

run on android unit:

通过JUnit进行Android单元测试

0 0