Android 在开发中使用单元测试

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u014099894/article/details/51276555

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

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

##简介Android里的测试

本地测试 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. *
本地单元测试,直接运行在JVM上,不用运行在Android设备上,以最大限度的减少时间。

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.
这种Instrumented单元测试必须要运行在Android设备上,因为他依赖的对象并不能被轻易模拟出来。

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.
UI测试保证了用户交互的正确性。
也可以人力测试,但那是费时费力的,自动化测试将会更快速,并且可以重用。

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.
在测试代码中,使用UI测试框架模拟用户交互来进行测试。

###4. gradle中添加依赖


  defaultConfig {
	  //add this
	  testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  }
	  
  //add this
  packagingOptions {
	  exclude 'LICENSE.txt'
  }

  dependencies {
	  //support annotations,必须添加,否则不匹配
	  androidTestCompile 'com.android.support:support-annotations:23.3.0'
	
	  //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 'com.android.support.test:runner:0.5'
	  androidTestCompile 'com.android.support.test:rules:0.5'
	
	  //use espresso,UI testing with Espresso, single app
	  androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
	
	  //use uiautomator,UI testing with UI Automator, crossing app. 这个需要 minSDK >= 18,如果不需要和其他app交互,可以注释该行
	  androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
	  
  }

下面对每个测试分类进行说明:

##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";

    @Mock
    Context mMockContext; //模拟Android对象

    @Test //添加注解表示这是一个测试方法
    public void readStringFromContext_LocalizedString() {
        // Given a mocked Context injected into the object under test... 使用when()和thenReturn()方法模拟依赖对象返回值
        when(mMockContext.getString(R.string.hello_word))
                .thenReturn(FAKE_STRING);
        
        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(""));
    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官网

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

	    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'

介绍ActivityTestRule:
*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进行测试。
@LargeTest
public class EspressoTest {

  private static final String STRING_TO_BE_TYPED = "peter";

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

  @Test  
  public void sayHello() {
    onView(withId(R.id.editText)).perform(typeText(STRING_TO_BE_TYPED),
        closeSoftKeyboard()); 
	
	//获取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中的方法进行测试。
       onView(withId(R.id.textView)).check(matches(withText(expectedText)));
  }
}

##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 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'

打开uiautomatorviewer工具,方便定位元素:
在sdk安装目录下,tools目录下打开uiautomatorviewer工具,可以查看当前页面元素。

示例(还是上面那个例子,用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"));
    //点击
    TestingExampleApp.click();
  }

@Test 
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"));
	//输入Peter
    editText.setText("Peter");

    device.wait(Until.hasObject(By.text("Say hello!")), 3000);
    //获取该按钮,并点击
    UiObject2 button = device.findObject(By.text("Say hello!"));
    button.click();
	
	//等待系统响应完
    device.waitForIdle(3000);
	
    UiObject targetTextView = device.findObject(new UiSelector().textContains("Hello"));
    
    //确认校验
    assertEquals("Hello, " + "Peter" + "!", targetTextView.getText());
  }
}

##结语:
本篇文章介绍了Android开发中可以使用的单元测试及其分类,之后就可以在开发中使用单元测试对代码进行测试,一定要养成写单元测试的习惯啊,相信之后的不断实践,会对单元测试使用的越来越熟练。

You should get into the habit of creating tests.

本篇文章中涉及到的教程:



>参考:

官方文档:
Best Practices for Testing
Android Testing Tools

Android Assert测试类继承关系

展开阅读全文

没有更多推荐了,返回首页