Android 在开发中使用单元测试

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

之前我们理解了 单元测试的基本概念,那么现在就来实用啦。即如何在Android开发中使用单元测试,即Android开发中的单元测试有哪些可以让我们使用。

本篇文章所涉及代码在github上: TestingExample,写了好久_


本地测试 Local tests no dependencies
little framework dependencies,can be mocked Instrumented tests framework dependencies UI test single app multiple apps


1. local tests:

*Unit tests that run on your local machine only. These tests are compiled to run locally on the Java Virtual Machine (JVM) to minimize execution time. *

Use this approach to run unit tests that have no dependencies on the Android framework or have dependencies that can be filled by using mock objects.
这种单元测试并不依赖Android framework,或者所依赖的对象能够被模拟出来。

2. instrumented tests

Unit tests that run on an Android device or emulator. These tests have access to instrumentation information, such as the Context for the app under test. Use this approach to run unit tests that have Android dependencies which cannot be easily filled by using mock objects.

Using instrumented unit tests also helps to reduce the effort required to write and maintain mock code. You are still free to use a mocking framework, if you choose.
这种测试方法可以不用写模拟对象的代码。当然,你仍然可以使用 Mockito 来模拟依赖对象,如果你选择这么做的话。

###3. UI Test
User interface (UI) testing lets you ensure that your app meets its functional requirements and achieves a high standard of quality such that it is more likely to be successfully adopted by users.
The automated approach allows you to run your tests quickly and reliably in a repeatable manner.

In your test code, you can use UI testing frameworks to simulate user interactions on the target app, in order to perform testing tasks that cover specific usage scenarios.

###4. gradle中添加依赖

  defaultConfig {
	  //add this
	  testInstrumentationRunner ""
  //add this
  packagingOptions {
	  exclude 'LICENSE.txt'

  dependencies {
	  //support annotations,必须添加,否则不匹配
	  androidTestCompile ''
	  //local unit test
	  testCompile 'junit:junit:4.12'
	  //local unit test, simple dependencies on Android framework
	  testCompile 'org.mockito:mockito-core:1.10.19'
	  // Optional -- Hamcrest library
	  androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
	  //add this
	  androidTestCompile ''
	  androidTestCompile ''
	  //use espresso,UI testing with Espresso, single app
	  androidTestCompile ''
	  //use uiautomator,UI testing with UI Automator, crossing app. 这个需要 minSDK >= 18,如果不需要和其他app交互,可以注释该行
	  androidTestCompile ''


##1. 本地测试Local Unit Test
Local Unit Test类文件放在 src/test/java/packageName 目录下。它是直接运行在JVM上的测试类,相当于Java单元测试,对Android frameword毫无依赖 或者 有很少的简单依赖。当有依赖时可用使用Instrumented Unit Tests,避免写很多依赖代码,当然,运行速度会降低(运行在JVM上当然快啦)。

Local Unit Test 有两种:

###1.1 不依赖Android framework,只使用JUnit 4
这种直接使用 JUnit 4 进行测试。和写Java的单元测试一样。
使用 @Test 表示这是一个测试方法,使用junit.Assert 一系列方法进行验证。

###1.2 有很少依赖Android framework
使用 Mockito 模拟依赖对象,模拟依赖对象的返回值。With Mockito, you can configure mock objects to return some specific value when invoked.

  1. 在类上添加注解 @RunWith(MockitoJUnitRunner.class)
  2. 使用 @mock 注解成员变量,表示这是一个mock object for an Android dependency
  3. 使用 when()thenReturn() 方法模拟该依赖对象返回值


@RunWith(MockitoJUnitRunner.class) //添加类注解
public class UnitTestSample {

    private static final String FAKE_STRING = "HELLO WORLD";

    Context mMockContext; //模拟Android对象

    @Test //添加注解表示这是一个测试方法
    public void readStringFromContext_LocalizedString() {
        // Given a mocked Context injected into the object under test... 使用when()和thenReturn()方法模拟依赖对象返回值
        ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);

        // ...when the string is returned from the object under test...
        String result = myObjectUnderTest.getHelloWorldString();

        // ...then the result should be the expected one.
        assertThat(result, is(FAKE_STRING));

##2. Instrumented Unit Tests
Instrumented Unit Test类文件放在 src/androidTest/java/packageName 目录下。它基于JUnit 4进行测试的。它必须要运行在机器上,因为对framework有依赖。

1.在类上添加注解 @RunWith(AndroidJUnit4.class)
2.使用 @SmallTest@MediumTest@LargeTest 表示该测试的依赖程度

Small: this test doesn't interact with any file system or network.
Medium: Accesses file systems on box which is running tests.
Large: Accesses external file systems, networks, etc.

3.和JUnit 4一样,使用注解 @Test 表示该方法是一个测试方法

使用test suite进行测试。

  1. 使用 @RunWith(Suite.class) 注解类
  2. 使用@Suite.SuiteClasses({XXX.class, XXX.class})包含所测试的类


// CommonUtil.isEmpty("")中依赖了 TextUtils类。
@RunWith(AndroidJUnit4.class) @SmallTest public class LocalInstrumentedUnitTest {
  @Before public void setUp() throws Exception {

  @Test public void testIsStringEmpty() throws Exception {
    assertTrue(CommonUtil.isEmpty("     "));

  @Test public void testIsAllStringEmpty() throws Exception {
    assertTrue(CommonUtil.isAllEmpty("", "   ", ""));
    assertFalse(CommonUtil.isAllEmpty("哈哈哈", "  "));

  @Test public void testIsOneStringEmpty() throws Exception {
    assertTrue(CommonUtil.isOneEmpty("", "   ", ""));
    assertTrue(CommonUtil.isOneEmpty("", "哈哈", "   "));
    assertFalse(CommonUtil.isOneEmpty("哈哈", "哈哈"));

##3. UI测试(单个app内)UI test:Single app
使用 The Espresso testing framework 在单个app内进行测试,**它可以运行在API >= 8的设备上。**测试代码必须要放在src/androidTest/java 目录下。

The Espresso testing framework is an instrumentation-based API and works with the AndroidJUnitRunner test runner.
更多介绍: Espresso官网

build.gradle 文件中添加如下依赖,请参考上面的:

	    androidTestCompile ''

*This rule provides functional testing of a single activity. The activity under test will be launched before each test annotated with Test and before methods annotated with Before. It will be terminated after the test is completed and methods annotated with After are finished. *
ActivityTestRule 提供了对单个Activity进行测试的方法,该框架会在启动该Activity前调用 @before 和 在该Activity结束时 调用@after


@RunWith(AndroidJUnit4.class)  //我们也可以继承ActivityInstrumentationTestCase2,但是建议使用JUnit 4 style进行测试。
public class EspressoTest {

  private static final String STRING_TO_BE_TYPED = "peter";

  @Rule //添加ActivityTestRule,注意泛型,测试MainActivity
  public ActivityTestRule<MainActivity> mActivityRule =
      new ActivityTestRule<>(MainActivity.class);

  public void sayHello() {
	//获取text为"Say hello!"的控件。onView()方法获取控件;preform()方法进行操作。
    onView(withText("Say hello!")).perform(click());

    String expectedText = "Hello, " + STRING_TO_BE_TYPED + "!";
    //Use the ViewAssertions methods to check.使用ViewAssertions中的方法进行测试。

##4. UI测试(多个app)UI test:multiple apps
使用 UI Automator testing framework 在多个app中进行测试。

The UI Automator APIs let you interact with visible elements on a device, regardless of which Activity is in focus. UI Automator tests can run on devices running Android 4.3 (API level 18) or higher.
UI Automator 可以让你和运行在设备上的可见元素进行交互,并不需要关心是哪个activity。它必须运行在API >= 18的机器上。


    androidTestCompile ''


示例(还是上面那个例子,用UI Automator重写):
关于使用UI Automator写单元测试,若是仅在single app内,还是用Espresso的好,Espresso的代码方便易于理解。

@RunWith(AndroidJUnit4.class) //注解
@SdkSuppress(minSdkVersion = 18) //注解
public class UiautomatorTestJunitStyle {
  private UiDevice device;

  @Before public void setUp() throws Exception {
	// 请手动滑动到该app所在页面,此处只模仿点击

    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    device.wait(Until.hasObject(By.text("TestingExample")), 3000);
	//获取text为 TestingExample 的 app
    UiObject2 TestingExampleApp = device.findObject(By.text("TestingExample"));

public void testSayHello() throws Exception {
    device.wait(Until.hasObject(By.text("hello_world")), 3000);
    device.wait(Until.hasObject(By.text("Enter your name here")), 3000);

	//获取text为Enter your name here的控件
    UiObject2 editText = device.findObject(By.text("Enter your name here"));

    device.wait(Until.hasObject(By.text("Say hello!")), 3000);
    UiObject2 button = device.findObject(By.text("Say hello!"));;
    UiObject targetTextView = device.findObject(new UiSelector().textContains("Hello"));
    assertEquals("Hello, " + "Peter" + "!", targetTextView.getText());


You should get into the habit of creating tests.



Best Practices for Testing
Android Testing Tools

Android Assert测试类继承关系