Android单元测试基本知识

单元测试简介

在Android Studio上进行单元测试是相对来说比较简单的,主要可以分为两类:

  • local unit test : 本地单元测试
    本地单元测试是跑在本地JVM上的,不依赖于Android设备,所以无法测试依赖于Andorid框架的代码。优点是运行速度快,可以直接访问你电脑上的本地资源。

在运行local unit test时,android.jar将不包含任何实际的代码,如果在运行local unit test时调用了android框架代码,那么将会报

Error: "Method... not mocked"

解决方案有两种:

  • 利用instrumentation test来进行测试

  • 在project build.gradle文件中添加

android {  ...  testOptions {    unitTests.returnDefaultValues = true  }}

这样所有的android框架代码的方法均会返回默认值0或者null。当然这并不是一种值得推荐的方法。

Android Studio默认使用JUnit来进行单元测试,但是你也可以Mockito等测试框架来拓展测试能力。

  • instrumentation test : 仪器单元测试
    仪器单元测试是跑在Android设备上的,可以用于测试依赖于Android框架的代码。Android Studio默认采用Espresso来进行测试。

instrumentation test实际上运行了两个app。第一个是被测试的app,第二个是instrumentation test所在的application,androidTest目录下其实包含了一个完整的工程,如manifest文件、资源文件等,只不过这些文件已经完全被隐藏。系统在同一个进程下运行这两个app,因此测试app可以调用app的方法和修改app的变量。

一般来说,这两种单元测试各有其优缺点,我一般用instrumentation test比较多。

编写单元测试

Android Studio默认已经给我们添加好了单元测试需要的依赖框架,我们只需要在相应的类或方法上,点击Ctrl+Shift+T生成相应的测试类,然后编写相应的测试代码即可。

for example:
新建一个Calculator类

public class Calculator {    public int add(int a, int b) {        return a = b;    }}

在Calculator类上点击ctrl+shift+T,直接在androidTest/java/目录下生成CalculatorTest.java文件。然后在CalculatorTest.java右键可以直接运行该单元测试文件。

public class CalculatorTest {    @Test    public void add() throws Exception {        Calculator calculator = new Calculator();        assertEquals(2, calculator.add(1, 1));    }}

为什么这里选择androidTest/java而不是test/java目录,因为前者是真正的Android设备环境,使用起来也很方便,并且后续生成测试代码覆盖率报告时也会很方便。

单元测试mock

有时我们想单独对某个模块进行测试,但是这个模块依赖于其他未完成的模块,这时我们可以使用单元测试的mock功能,单元测试的mock也分为两种:

local unit test的mock

常用的local unit test单元测试框架为mockito,使用方法分为如下几个步骤:
1、在module的build.gradle文件中添加mockito依赖

testCompile 'org.mockito:mockito-core:1.10.19'  // 注意这里testCompile和androidTestCompile的区别,testCompile是对local unit test的,而androidTestCompile针对于instrumentation test

2、在测试的定义处添加注解

@RunWith(MockitoJUnitRunner.class)

3、创建mock对象,并在定义处添加@Mock注解
4、构建when()… then()… 语句定义mock的方法和返回值。

@RunWith(MockitoJUnitRunner.class)public class CalculatorTest {    @Mock    Calculator calculator;    @Test    public void add() throws Exception {        when(calculator.add(3,2))                .thenReturn(4);        assertEquals(4, calculator.add(1, 2));    }}

这里有几个值得注意的事:

  • testCompile ‘org.mockito:mockito-core:1.10.19’,不要写为androidTestCompile ‘org.mockito:mockito-core:1.10.19’,否则会找不到mockito依赖。

  • 如果when…then… 语句中定义的参数和实际调用的不一致,那么如果返回值是基本类型,则返回空,否则返回null。

网络服务的单元测试

一般对于网络服务单元测试有两种选择,一种是对网络服务进行mock,另一种是直接访问网络。这里我更倾向于第二种方式,因为首先mock那么多的网络接口也是一种麻烦事,其次mock的接口的返回情况可能和实际值也会存在一定的偏差。

网络服务一般也可以分为两种,第一种为同步服务,另一种为异步服务。同步服务直接调用接口获取返回值并对返回值进行判断即可。而异步稍微有点麻烦。

要想对异步服务进行测试,我们首先要清楚Android Studio对单元测试代码运行的基本原理。Android Studio采用了一个单线程,(除了@Before和@After),不断调用@Test方法(不保证调用的顺序),一定要注意的是assert方法必须要在该线程中执行才会有效。知道了这点,异步服务的单测也就明朗了。
我们在发送请求后,将单元测试线程阻塞住,等到获取服务器返回的结果后,再释放单元测试线程,并在单元测试线程中执行assert语句即可。

同步网络服务单元测试

@Testpublic void createDir() throws Exception {    CreateDirRequest createDirRequest = new CreateDirRequest();    CreateDirResult createDirResult = client.createDir(createDirRequest); // 同步网络服务接口    assertEquals(true, isSuccess(createDirResult)); // 判断网络任务是否正确执行}

异步网络服务单元测试

    @Test    public void createDirAsyn() throws Exception {        final CountDownLatch countDownLatch = new CountDownLatch(1);        CreateDirRequest createDirRequest = new CreateDirRequest();        unitTestResult = false; // 用于记录执行结果        createDirRequest.setListener(new ICmdTaskListener() {            @Override            public void onSuccess(COSRequest cosRequest, COSResult cosResult) {                unitTestResult = isSuccess(cosResult);                countDownLatch.countDown();            }            @Override            public void onFailed(COSRequest cosRequest, COSResult cosResult) {                unitTestResult = isSuccess(cosResult)                 countDownLatch.countDown();            }        });        client.createDir(createDirRequest); // 执行异步任务        countDownLatch.await(); // 等待任务执行结束        assertEquals(true, unitTestResult); // 判断执行结果    }

单元测试相关命令

1、运行local unit test

gradlew testDebugUnitTest

2、运行instrumentation test并生成测试报告

gradlew createDebugCoverageReport

注意想要生成代码覆盖率报告,需要在module的build.gradle文件下添加

buildTypes {    debug {        testCoverageEnabled true    }}

单元测试常见问题:

如何获取Context

1Context contextnew MockContext();2Context context = InstrumentationRegistry.getContext(); // 获取测试app的context3Context context = InstrumentationRegistry.getTargetContext(); // 获取被测试app的context

第一种方式获取的context在调用getSystemService()方法时或报错,而后两种不会。

日志输出

我在Android Studio 2.3版本上运行instrumentation test,并在Android Monitor中不选择任何过滤器,是可以查看日志的。但是同事在Android Studio 2.1版本上却无法打印日志。

文件读取Permission Denied

场景:自己在Android项目下新建了一个module,其中有个方法调用了写文件代码,然后在进行单元测试时总是报:

java.io.FileNotFoundException: /storage/emulated/0/test.txt: open failed: EACCES (Permission denied)

原因:需要在该module上添加权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

其次由于Android6.0的动态权限特性,如果是targetSdkVersion >= 23,且运行单元测试的手机系统大于或者等于Android6.0,需要降低targetSdkVersion或者使用低于Android6.0的手机。

Test run failed: Instrumentation run failed due to ‘Process crashed.’

是否添加了如下依赖:

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

junit.framework.AssertionFailedError: No tests found in com.tencent.cos.COSClientTest

是否配置了

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

no test were found

这个错误导致的原因有很多,基本上都是使用或者配置上的错误。

org.mockito.exceptions.misusing.MissingMethodInvocationException:

public class CalculatorTest {    @Mock    Calculator calculator;    @Test    public void add() throws Exception {        //calculator = new Calculator();    // 这里不能再初始化        when(calculator.add(1,2))                .thenReturn(4);        calculator = new Calculator();        assertEquals(4, calculator.add(1, 2));    }}

.Instrumentation run failed due to ‘java.lang.NoClassDefFoundError

在单独运行每个测试类时运行正常,但是调用./gradlew connectedAndroidTest命令来运行单元测试就会报如上错误。更奇葩的是自己在api level为16的模拟器上没有这个问题,在api level为23的真机上就会出现这个问题。

这里值得注意的是instrumentation test并不强制需要application module,只包含library module完全可以进行单元测试

检查了一下午,才发现fastjson和testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”有冲突,奇葩的是在1.2.35以及之前的版本存在这个问题,1.2.36和1.2.37就会有这个问题。

更多相关文章

  1. 老项目在Android(安卓)Studio 3.6.1不能运行解决办法和大致原理
  2. 【进阶】从linux到android,进程的方方面面
  3. Android上运行Http Server
  4. Android(安卓)SDK 2.0安装、配置图文教程
  5. Ubuntu for Android:共享同一linux内核,运行桌面(传统鼠标模式)和手
  6. Android——Tomcat+MySQL+Servlet,实现将Client传入的数据写入MyS
  7. android studio手机连接本地服务器测试
  8. Android程序在genymotion模拟器上能够成功安装但无法运行,点击app
  9. Android(安卓)sdk manager无法启动之swt.jar文件没有指定

随机推荐

  1. aipai服务架构
  2. 量化交易
  3. 关于分库分表后的数据统计异构方案
  4. 已安装nginx动态添加模块
  5. ce内存寻址基址
  6. h5图片展示和ajax上传
  7. downloadonly使用小技巧,快速给无外网系统
  8. vSAN集群 无法识别磁盘处理
  9. 关于rabbitmq与kafka的异同
  10. laravel相关备忘