写在开头
突然想把最近工作中研究的自动化测试总结一下了,期间也是伴随着查各种资料,不过还是有坑需要自己填,不过一篇文章应该也写不清楚吧。
一、单元测试及其必要性
先说一些比较官方的概念,测试这门技术相信大学期间学过软件工程这门课的都很熟悉,初次看见能够用代码实现对代码的测试还是感觉很神奇的,虽然之后就没用过了。没错单元测试本质上就是检测代码正确性的代码,一般由开发人员编写。单元测试在实际项目开发中还是有它的必要性的,如代码优化,第一手的文档,在Android中更能节省迭代的时间。也是由于需要去写代码并且工作量不亚于实际项目中的编码量,导致了开发人员很少去重视。
二、Android的单元测试
现在开发Android项目应该都是使用Android Studio(AS)这个编译器了,本文也主要是围绕它来讲解。首先从项目创建和目录结构讲起。
2.1 AS创建Android项目
应该所有人都注意过,使用AS创建的Android项目之后src下会有三个根目录分别是androidTest、main和test。
其中main里面就是主工程项目的文件,另外两个文件都是和测试相关,他们区别主要是第三方库的引用上的不同,查看build.gradle文件可见如下:
使用implementation导入的库在main主项目中使用,testImplementation导入的库在test项目中使用,androidTestImplementation导入的库在androidTest项目中使用。注意,这里的AS版本是3.x,implementation和api是最新的导入库的关键字,如果是2.x版本还是原来的compile。也就是说junit这个库只能在test中使用,espresso只能在androidTest目录中使用,也曾经试过两种方式导入junit,效果如何可以自行体会。
2.2 一个Junit的例子
这个例子是改造了test目录下默认的ExampleUnitTest测试类,为它在主工程下添加了一个被测试类ExampleUnit。
//main目录下的被测试类
public class ExampleUnit {
public static int add(int a, int b) {
return a + b;
}
}
//test目录下改造之后的测试类
public class ExampleUnitTest {
@Test // 1
public void addition_isCorrect() throws Exception {
assertEquals(4, ExampleUnit.add(2, 2)); // 2
}
}
代码注释1处是Junit的注解,代表这个方法是个测试用例,一个测试类中可以用n个测试用例;注释2处是期望值和实际值的对比,assertEquals的第一个参数是期望值,第二个参数是实际值,这里调用了被测试类的add方法得到实际值,如果两个值相等该测试用例通过测试。
2.3 Robolectric单元测试
Robolectric是一个使用Android SDK的单元测试框架,可以用于Android程序的测试驱动开发。简单来说就是在JVM环境中运行Android的程序,不需要安装apk也不需要Android设备,能够做到快速测试。
testImplementation 'org.robolectric:robolectric:3.6.1'
官网上的使用介绍就是上面那样的,可能最新版本包括了所有需要的库了,旧版本使用的时候还要额外的相关库,具体的可以去Maven Repository查看。其实这个库在单元测试时也是使用Junit架构的,额外的支持了Android SDK,比如四大组件啊和Android常用的api。
Robolectric特点就是可以直接对四大组件进行单元测试,无需外接任何模拟器和真机设备,这里就以常见的Activity为例。涉及到Activity,首先就会想到布局文件,测试的时候控件的使用是离不开的,为了能够使用这些资源,需要在gradle中额外添加支持的代码。
android {
...
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
Activity里面的代码就不贴出来了,就是点击一个Button然后弹出一个Toast,测试的内容就是这个Toast是否弹出并且显示的内容是我们想要的。
@RunWith(RobolectricTestRunner.class) // 1
public class MainActivityTest{
@Before
public void setUp() throws Exception {
ShadowLog.stream = System.out; // 2
}
@Test
public void test() throws Exception {
MainActivity activity = Robolectric.setupActivity(MainActivity.class); // 3
activity.findViewById(R.id.button).performClick(); // 4
Log.i(TAG, ShadowToast.getTextOfLatestToast());
assertEquals("This is MainActivity", ShadowToast.getTextOfLatestToast()); // 5
}
}
- 注释1,含义就是测试使用的框架是Robolectric,其他的还有诸如AndroidJUnit4.class这个可以见androidTest文件夹下默认创建的测试类;
- 注释2,可以把System.out输出的流转换成log,这是Robolectric提供的功能,更符合Android的代码编写风格,Before注解代表了这个方法会在所有测试用例之前执行。
- 注释3,获取MainActivity的实例。
- 注释4,获取Button的id并实现点击处理。
- 注释5,getTextOfLastestToast()可以获取Toast显示的内容,为了更好的演示特意使用log输出这个Toast内容
最终的测试结果如下,那三个警告可以先忽略不计,测试用例显示为蓝色证明测试通过了。
三、Gradle的自动化测试
以上讲的一些例子,虽说已经能够实现单元测试,而且在一个测试类中写上数个测试用例(即方法)已经能够达到一定程度的自动化,但是和真正的让程序自己自动化去测试同时生成测试报告并打包成apk还未达到,这里就重点讲这个了。Gradle的命令中可以实现自动化运行所有的测试用例
在Android项目编译的时候其实Gradle进行N多个Task的运行,这也是使用Gradle之后编译程序速度略慢的一个原因吧,可以点开Gradle栏(一般默认在右上角)查看有什么Task,这里主要介绍和测试有关的。
这里可以比较明显的看到几个带有Test的Task,明白androidTest和test区别也就知道这里的区分了,connectedAndroidTest是运行androidTest目录下的测试用例,test任务运行test目录下的测试用例,单独运行这两个task就可以得到测试报告了。可以直接AS的终端运行gradlew test,假设一切正常没有出错,会在build目录下得到新的文件。
reports文件夹下的内容就是测试报告了,test-results也是新生成的,不过内容不需要关注。点开index.html就可以查看测试报告了,差不多就是下面这样的。
四、总结
本篇讲了AS下的单元测试如何去实现并且利用Gradle生成测试报告,下一篇可以利用jacoco插件生成代码覆盖率报告,并且在持续集成工具jenkins上实现真正的自动化测试。特别要说明一下,以上实验环境的搭建是AS3.x版本适用,低版本的具体配置会有不同。