Android单元测试那些事儿(一)
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
1、Context context = new MockContext();2、Context context = InstrumentationRegistry.getContext(); // 获取测试app的context3、Context 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就会有这个问题。
更多相关文章
- 老项目在Android(安卓)Studio 3.6.1不能运行解决办法和大致原理
- 【进阶】从linux到android,进程的方方面面
- Android上运行Http Server
- Android(安卓)SDK 2.0安装、配置图文教程
- Ubuntu for Android:共享同一linux内核,运行桌面(传统鼠标模式)和手
- Android——Tomcat+MySQL+Servlet,实现将Client传入的数据写入MyS
- android studio手机连接本地服务器测试
- Android程序在genymotion模拟器上能够成功安装但无法运行,点击app
- Android(安卓)sdk manager无法启动之swt.jar文件没有指定