从图3-2中可以看到,基本所有的测试用例都是通过InstrumenationTestRunner执行的,而各个测试类型被设计来执行特定的测试,在本章中,主要讲解ActivityInstrumentationTestCase2的用法,其他的类型将放在后续章节里讲解。ActivityInstrumentationTestCase2这个类型是用来针对单个活动执行功能测试。它通过InstrumentationTestCase.launchActivity函数使用系统API来创建待测活动,可以在这个测试用例里直接操控活动,在待测应用的UI线程上执行测试函数,也允许我们向待测应用注入一个自定义的意图对象。
3.2.1 ActivityInstrumentationTestCase2测试用例
一个ActivityInstrumentationTestCase2的测试用例源码框架一般如代码清单3-5所示。
代码清单3-5 ActivityInstrumentationTestCase2测试用例的源码框架
- 1. public class SpinnerActivityTest extends
- 2. ActivityInstrumentationTestCase2<SpinnerActivity>{
- 3. publicSpinnerActivityTest() {
- 4. super(SpinnerActivity.class);
- 5. }
- 6.
- 7. @Override
- 8. protected void setUp() throws Exception {
- 9. super.setUp();
- 10. // 添加自定义的初始化逻辑
- 11. }
- 12.
- 13. @Override
- 14. protected void tearDown() throws Exception {
- 15. super.tearDown();
- 16. }
- 17.
- 18. public void test测试用例() throws Exception {
- 19. // ...
- 20. }
- 21. }
ActivityInstrumentationTestCase2泛型类的参数类型是MainActivity,这样就指定了测试用例的待测活动,而且它只有一个构造函数——需要一个待测活动类型才能创建测试用例,其函数声明如下:
- ActivityInstrumentationTestCase2(Class<T> activityClass)
传递的活动类型应该跟泛型类参数保持一致,代码清单3-5的第1~5行就演示了这个要求。
在Android SDK的示例工程“SpinnerTest”中,有一个很完整的ActivityInstrumentationTestCase2测试用例的示例,演示了Android仪表盘测试用例的一些最佳实践,如代码清单3-6所示。为了方便读者阅读,笔者将其中的注释用中文翻译过来。
在启动待测活动之前,先将触控模式禁用,以便控件能接收到键盘消息,如代码清单3-6的第54行。这是因为在Android系统里,如果打开触控模式,有些控件是不能通过代码的方式设置输入焦点的,手指戳到一个控件后该控件自然而然就获取到输入焦点了,例如戳一个按钮除了导致其获取输入焦点以外,还触发了其单击事件。而如果设备不支持触摸屏,例如老式的手机,需要先用方向键导航到按钮控件使其高亮显示,然后再按主键来触发单击事件。在Android系统中,出于多种因素的考虑,在触控模式下,除了文本编辑框等特殊的控件,可触控的控件如按钮、下拉框等无法设置其具有输入焦点。这样在自动化测试时,就会导致一个严重的问题,因为无法设置输入焦点,在发送按键消息时,就没办法知道哪个控件最终会接收到这些按键消息,一个简单的方案就是,在测试执行之前,强制待测应用退出触控模式。这样在93行,我们才能在代码中设置具有输入焦点的控件。
在测试集合中,应该有一个测试用例验证待测活动是否正常初始化,如69~78行之间的testPreconditions函数。
对界面元素的操作必须放在UI线程中执行,如90~97行的代码块。
代码清单3-6 Android示例工程SpinnerTest里的最佳实践
- 1. package com.android.example.spinner.test;
- 2.
- 3. import com.android.example.spinner.SpinnerActivity;
- 4.
- 5. import android.test.ActivityInstrumentationTestCase2;
- 6. import android.view.KeyEvent;
- 7. import android.widget.Spinner;
- 8. import android.widget.SpinnerAdapter;
- 9. import android.widget.TextView;
- 10.
- 11. public class SpinnerActivityTest
- 12. extends ActivityInstrumentationTestCase2<SpinnerActivity> {
- 13. // 下拉框选项数组mLocalAdapter中的元素个数
- 14. public static final int ADAPTER_COUNT = 9;
- 15.
- 16. // Saturn这个字符串在下拉框选项数组mLocalAdapter的位置(从0开始计算)
- 17. public static final int TEST_POSITION = 5;
- 18.
- 19. // 下拉框的初始位置应该是0
- 20. public static final int INITIAL_POSITION = 0;
- 21.
- 22. // 待测活动的引用
- 23. private SpinnerActivity mActivity;
- 24.
- 25. // 待测活动上下拉框当前显示的文本
- 26. private String mSelection;
- 27.
- 28. // 下拉框当前的选择的位置
- 29. private int mPos;
- 30.
- 31. // 待测活动里的下拉框对象的引用,通过仪表盘API来操作
- 32. private Spinner mSpinner;
- 33.
- 34. // 待测活动里下拉框的数据来源对象
- 35. private SpinnerAdapter mPlanetData;
- 36.
- 37. /*
- 38. * 创建测试用例对象的构造函数,必须在构造函数里调用基类
- 39. * ActivityInstrumentationTestCase2的构造函数,传入
- 40. * 待测活动的类型以便系统到时可以启动活动
- 41. */
- 42. public SpinnerActivityTest() {
- 43. super(SpinnerActivity.class);
- 44. }
- 45.
- 46. @Override
- 47. protected void setUp() throws Exception {
- 48. // JUnit要求TestCase子类的setUp函数必须
- 49. // 调用基类的setUp函数
- 50. super.setUp();
- 51.
- 52. // 关闭待测应用的触控模式,以便向下拉框发送按键消息
- 53. // 这个操作必须在getActivity()之前调用
- 54. setActivityInitialTouchMode(false);
- 55.
- 56. // 启动待测应用并打开待测活动。
- 57. mActivity = getActivity();
- 58.
- 59. // 获取待测活动里的下拉框对象,这样也可以确保待测活动
- 60. // 正确初始化
- 61. mSpinner = (Spinner)mActivity.findViewById(
- 62. com.android.example.spinner.R.id.Spinner01);
- 63. mPlanetData = mSpinner.getAdapter();
- 64. }
- 65.
- 66. // 测试待测应用的一些关键对象的初始值,以此确保待测应用
- 67. // 的状态在测试过程中是有意义的,如果这个测试用例(函数)
- 68. // 失败了,基本上可以忽略其他测试用例的测试结果
- 69. public void testPreconditions() {
- 70. // 确保待测下拉框的选择元素的回调函数被正确设置
- 71. assertTrue(mSpinner.getOnItemSelectedListener() != null);
- 72.
- 73. // 验证下拉框的选项数据初始化正常
- 74. assertTrue(mPlanetData != null);
- 75.
- 76. // 并验证下拉框的选项数据的元素个数是正确的
- 77. assertEquals(mPlanetData.getCount(), ADAPTER_COUNT);
- 78. }
- 79.
- 80. // 通过向待测活动的界面发送按键消息,在验证下拉框的状态
- 81. // 是否与期望的一致
- 82. public void testSpinnerUI() {
- 83. // 设置待测下拉框控件具有输入焦点,并设置它的初始位置。
- 84. // 因为这段代码需要操作界面上的控件,因此需要运行在
- 85. // 待测应用的线程中,而不是测试用例线程中
- 86. //
- 87. // 只需要将要在UI线程上执行的代码作为参数传入runOnUiThread
- 88. // 函数里就可以了,代码块是放在Runnable匿名对象
- 89. // 的run()函数里
- 90. mActivity.runOnUiThread(
- 91. new Runnable() {
- 92. public void run() {
- 93. mSpinner.requestFocus();
- 94. mSpinner.setSelection(INITIAL_POSITION);
- 95. }
- 96. }
- 97. );
- 98.
- 99. // 使用手机物理键盘上方向键的主键激活下拉框
- 100. this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- 101.
- 102. // 向下拉框发送5次向“下”按键消息
- 103. // 即高亮显示下拉框的第5个元素
- 104. for (int i = 1; i <= TEST_POSITION; i++) {
- 105. this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
- 106. }
- 107.
- 108. // 选择下拉框当前高亮的元素
- 109. this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- 110.
- 111. // 获取被选元素的位置
- 112. mPos = mSpinner.getSelectedItemPosition();
- 113.
- 114. // 从下拉框的选项数组mLocalAdapter中获取被选元素的数据
- 115. // (是一个字符串对象)
- 116. mSelection = (String)mSpinner.getItemAtPosition(mPos);
- 117.
- 118. // 获取界面上显示下拉框被选元素的文本框对象
- 119. TextView resultView = (TextView) mActivity.findViewById(
- 120. com.android.example.spinner.R.id.SpinnerResult);
- 121.
- 122. // 获取文本框的当前文本
- 123. String resultText = (String) resultView.getText();
- 124.
- 125. // 验证下拉框显示的值的确是被选的元素
- 126. assertEquals(resultText,mSelection);
- 127. }
- 128. }