Android单元测试

单元测试

单元测试是针对某个单元进行测试,单元可大可小,可能是一个框架的核心处理流程,一个模块的控制逻辑,一个类的实现或者一个函数。在这里你只需要关心一点,那就是单元测试的单元是有边界的,在任何时候你要开始单元测试,请确定已经划分好你的单元。

单元测试其测试方式主要是以白盒为主,是最接近开发的测试是可以最早发现问题的测试,在这一步发现问题,就可以有效避免问题在后期被的无限放大。

单元测试的最大特点是模拟输入输出,任何时候获取输入或者输出的成本太高的时候,都可以考虑使用单元测试。测试对象: 非UI的Class都可以进行单元测试

单元测试让你在测试的时候只关心被测试单元,而不必为其输入输出分散精力,比如使用调试的方式来测试一个有8个if-else分支的逻辑,你要启动八次(大约)程序,然后进行一系列操作,这么多事情,最终只是为某个用例创造输入,单元测试里则直接指定输入,一次到位。

Android测试

测试函数需要@Test注解 && public声明

  • 测试一些数据性的功能,比如加载数据
  • 测试SharedPerferences,测试数据库,测试函数等
  • 工具类的测试,比如计算,验证时间,转化格式,正则验证等等

单元测试

直接在JVM上运行测试,在没有依赖或者仅仅只需要简单的Android库,相当于Java单元测试

测试代码目录为:app/src/test/java/packagename

1
testCompile 'junit:junit:4.12'
Annotation 描述
@Test public void method() 定义所在方法为单元测试方法
@Test (expected = Exception.class) 如果所在方法没有抛出Annotation中的Exception.class->失败
@Test(timeout=100) 如果方法耗时超过100毫秒->失败
@Test(expected=Exception.class) 如果方法抛了Exception.class类型的异常->通过
@Before public void method() 这个方法在每个测试之前执行,用于准备测试环境(如: 初始化类,读输入流等)
@After public void method() 这个方法在每个测试之后执行,用于清理测试环境数据
BeforeClass public static void method() 这个方法在所有测试开始之前执行一次,用于做一些耗时的初始化工作(如: 连接数据库)
AfterClass public static void method() 这个方法在所有测试结束之后执行一次,用于清理数据(如: 断开数据连接)

模拟测试

需要在Android设备上运行测试,主要用于测试界面与业务逻辑

主要三点:

  • UI加载好后展示的信息是否正确。
  • 在用户某个操作后UI信息是否展示正确。
  • 展示正确的页面供用户操作

测试代码目录为: app/src/androidTest/java/packagename

1
2
3
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 对于Id为R.id.my_view的View: 触发点击,检测是否显示
onView(withId(R.id.my_view)).perform(click()).check(matches(isDisplayed()));
// 对于文本打头是"ABC"的View: 检测是否没有Enable
onView(withText(startsWith("ABC"))).check(matches(not(isEnabled()));

// 按返回键
pressBack();
// 对于Id为R.id.button的View: 检测内容是否是"Start new activity"
onView(withId(R.id.button)).check(matches(withText(("Start new activity"))));
// 对于Id为R.id.viewId的View: 检测内容是否不包含"YYZZ"
onView(withId(R.id.viewId)).check(matches(withText(not(containsString("YYZZ")))));
// 对于Id为R.id.inputField的View: 输入"NewText",然后关闭软键盘
onView(withId(R.id.inputField)).perform(typeText("NewText"), closeSoftKeyboard());
// 对于Id为R.id.inputField的View: 清除内容
onView(withId(R.id.inputField)).perform(clearText());

JUnit常用语法

测试数组相等

1
2
3
4
5
6
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
}

测试相等

1
2
3
4
@Test
public void testAssertEquals() {
Assert.assertEquals("failure - strings are not equal", "text", "text");
}

测试条件(True/False)

1
2
3
4
5
6
7
8
@Test
public void testAssertFalse() {
Assert.assertFalse("failure - should be false", false);
}
@Test
public void testAssertTrue() {
Assert.assertTrue("failure - should be true", true);
}

测试空值(Null/Not Null)

1
2
3
4
5
6
7
8
@Test
public void testAssertNotNull() {
Assert.assertNotNull("should not be null", new Object());
}
@Test
public void testAssertNull() {
Assert.assertNull("should be null", null);
}

测试相同

1
2
3
4
5
6
7
8
9
@Test
public void testAssertNotSame() {
Assert.assertNotSame("should not be same Object", new Object(), new Object());
}
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
Assert.assertSame("should be same", aNumber, aNumber);
}

测试异常

1
2
@Test(expected= IndexOutOfBoundsException.class)
public void empty() { new ArrayList<Object>().get(0); }

测试超时

1
2
3
4
5
6
  @Test(timeout=1000)
public void testWithTimeout() {
// while(true){
//
// }
}

测试Rule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
@Test
public void countsAssets() throws IOException {
File icon = tempFolder.newFile("icon.png");
Assert.assertTrue(icon.exists());
}

@Before
public void setUp() throws Exception {
if (mCalculator == null){
mCalculator = new Calculator();
}
}

自定义Rule Rule的作用是你不必在每一个类的测试代码中写@Before, @After而仅仅需要定义一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RunWith(AndroidJUnit4.class)
public class SecondActivityTest {
@Rule
public ActivityTestRule<SecondActivity> rule =
new ActivityTestRule(SecondActivity.class, true,false);
@Test
public void demonstrateIntentPrep() {
Intent intent = new Intent();
intent.putExtra("EXTRA", "Test");
// 启动SecondActivity并传入intent
//测试Activity生命周期等
rule.launchActivity(intent);//
// 对于Id为R.id.display的View: 检测内容是否是"Text"
onView(withId(R.id.display)).check(matches(withText("Test")));
}
}

AssertJ Android

提高断言可读性:AssertJ Android

  • AssertJ Android:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    assertThat(layout).isVisible()
    .isVertical()
    .hasChildCount(4)
    .hasShowDividers(SHOW_DIVIDERS_MIDDLE);

    assertThat(layout.getVisibility()).isEqualTo(View.VISIBLE);
    assertThat(layout.getOrientation()).isEqualTo(VERTICAL);
    assertThat(layout.getChildCount()).isEqualTo(4);
    assertThat(layout.getShowDividers()).isEqualTo(SHOW_DIVIDERS_MIDDLE);
  • Regular JUnit:

    1
    2
    3
    4
    assertEquals(View.VISIBLE, layout.getVisibility());
    assertEquals(VERTICAL, layout.getOrientation());
    assertEquals(4, layout.getChildCount());
    assertEquals(SHOW_DIVIDERS_MIDDLE, layout.getShowDividers());

本文作者: Kyle

本文链接: https://zhutiankang.github.io

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!

Kyle wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!