建议各类测试所占比例如下:小型测试(单元测试)占 70%,中型测试(集成测试)占 20%,大型(端到端)测试占 10%。
单元测试分为:
1. 本地单元测试
单元测试编写目录对应test目录
1.1 Java 中的 JUnit 测试框架
AndroidStudio 新建项目会自动配置 JUnit 测试框架
1 2 3 4 5
| dependencies { ... testImplementation 'junit:junit:4.12' ... }
|
简单的测试案例:
1 2 3 4 5 6
| class ExampleUnitTest { @Test fun addition_isCorrect() { assertEquals(4, 2 + 2) } }
|
JUnit 中的常用注解:
- @Before:执行每个@Test方法之前调用
- @After:执行每个@Test方法之后调用
- @Test:
- @BeforeClass:在执行一个测试类的所有测试方法之前
- @AfterClass:在执行一个测试类的所有测试方法之后
- @Ignore:测试类中某些测试方法可能暂时处于不可测试阶段,那么为了保证这个测试类的正常运行,可以使用@Ignore标记这些测试方法。
1.2 单元测试框架Robolectric
Robolectric 的设计思想是通过 JVM 运行Android代码,从而实现脱离Android环境进行测试。Robolectric 框架依赖于 JUnit,所以需要同时添加两个框架依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| android {
testOptions { unitTests { includeAndroidResources = true } } } dependencies { ... testImplementation 'junit:junit:4.12' testImplementation 'org.robolectric:robolectric:4.3' ... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @RunWith(RobolectricTestRunner.class) public class MainActivityTest {
@Test public void titleIsCorrect() { MainActivity activity = Robolectric.setupActivity(MainActivity.class); assertEquals("TestingSample", activity.getTitle()); } @Test public void testLifecycle(){ ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class) .create().start(); Activity activity = controller.get(); TextView textView =activity.findViewById(R.id.login); assertEquals("onCreate()",textView.getText().toString()); controller.resume(); assertEquals("onResume()",textView.getText().toString()); } }
|
1.3 插桩测试(Mockito)
Mockito 环境配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| android {
testOptions { unitTests { includeAndroidResources = true } } } dependencies { ... testImplementation 'junit:junit:4.12' testImplementation 'org.robolectric:robolectric:4.3' testImplementation 'org.mockito:mockito-core:2.19.0' ... }
|
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
| public class ExampleUnitTest {
@Test public void mockTest(){
List mockedList = Mockito.mock(List.class);
mockedList.add("one"); mockedList.clear();
Mockito.verify(mockedList).add("one"); Mockito.verify(mockedList).clear();
LinkedList linkedList = Mockito.mock(LinkedList.class);
when(linkedList.get(0)).thenReturn("first"); when(linkedList.get(1)).thenReturn(new RuntimeException());
System.out.println(linkedList.get(0));
System.out.println(linkedList.get(1));
System.out.println(linkedList.get(999));
} }
|
2. 界面测试
界面测试编写目录对应androidTest目录。
2.1 单个应用的界面测试(Espresso)
添加Espresso依赖
1 2 3 4 5
| dependencies { ... androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' ... }
|
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
| @RunWith(AndroidJUnit4.class) public class ChangeTextBehaviorTest {
private String stringToBetyped; @Rule public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);
@Before public void initValidString() { stringToBetyped = "Espresso"; }
@Test public void changeText_sameActivity() { onView(withId(R.id.editTextUserInput)).perform(typeText(stringToBetyped), ViewActions.closeSoftKeyboard()); onView(withId(R.id.changeTextBt)).perform(click()); onView(withId(R.id.textToBeChanged)) .check(matches(withText(stringToBetyped))); } }
|
2.2 多个应用的界面测试(UIAutomator)
添加UIAutomator依赖:
1 2 3 4 5
| dependencies { ... androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' ... }
|
设计测试案例之前,需要确保UI Anutomator可以访问你的组件,即组件是否具有可见性或是否设置 android:contentDescription 属性。
2.2.1 uiautomatorviewer 工具
使用 uiautomatorviewer 的步骤:
- 在你模拟器或者测试设备上打开要测试的应用,并连接到AndroidStudio
- 前往 Android/sdk/tools/bin 目录打开 uiautomatorviewer 工具。
- 在 uiautomatorviewer 界面选择 device screenshot ,在右侧即可看见组件具体信息。
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
| @RunWith(AndroidJUnit4.class) @SdkSuppress(minSdkVersion = 18) public class UIAutomatorChangeTextBehaviorTest {
private static final String BASIC_SAMPLE_PACKAGE = "com.markzl.android.testingsample";
private static final int LAUNCH_TIMEOUT = 5000;
private static final String STRING_TO_BE_TYPED = "UiAutomator";
private UiDevice mDevice;
@Before public void startMainActivityFromHomeScreen() {
mDevice = UiDevice.getInstance(getInstrumentation()); mDevice.pressHome();
final String launcherPackage = getLauncherPackageName(); assertThat(launcherPackage, notNullValue()); mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);
Context context = getApplicationContext(); final Intent intent = context.getPackageManager() .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent);
mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT);
}
@Test public void checkPreconditions(){ assertThat(mDevice,notNullValue()); }
@Test public void testChangeText_sameActivity(){ mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE,"editTextUserInput")) .setText(STRING_TO_BE_TYPED); mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE,"changeTextBt")) .click();
UiObject2 changedText = mDevice.wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE,"textToBeChanged")), 500); assertThat(changedText.getText(),is(equalTo(STRING_TO_BE_TYPED))); }
@Test public void testChangeText_newActivity(){ mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE,"editTextUserInput")) .setText(STRING_TO_BE_TYPED); mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE,"activityChangeTextBtn")) .click();
UiObject2 changedText = mDevice.wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE,"show_text_view")), 500); assertThat(changedText.getText(),is(equalTo(STRING_TO_BE_TYPED)));
}
private String getLauncherPackageName() {
final Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME);
PackageManager packageManager = getApplicationContext().getPackageManager(); ResolveInfo resolveInfo = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); return resolveInfo.activityInfo.packageName;
}
}
|
UIAutomator API:
- UiCollection
- UiObject
- UiScrollable
- UiSelector
- Configurator