简介
关于单元测试的工具调研可以参考:https://km.sankuai.com/page/365031302
在构建本地单元测试的时候,JUnit4测试框架是java标准测试库,junit测试框架可以让你在编写测试代码的中进行setup, 卸载, 和断言等操作。构建本地单元测试框架运行不需要依赖于真实设备或者模拟设备,可以通过Robolectric库来实现对Android系统框架的依赖,Robolectric库是可以兼容junit库的。
通过junt4进行本地单元测试,不要依赖于android运行环境,因此运行速度更快,可以配合Truth库快速测试某个方法是否符合预期的程序设计。在编写测试代码的时候,可以按照业务实现类来新建测试类,每个Junit4测试类对应一个需要测试的业务代码,每个被@Test注解的测试方法是需要被测试的public方法用于验证业务功能等。
测试环境搭建
在程序中引入junit库,在moudule的build.gradle中配置,其中truth库是为了断言使用。
testImplementation 'junit:junit:4.12'
//断言库,替换junit的api
testImplementation "com.google.truth:truth:1.0.1"
在 Android Studio 项目中,将本地单元测试的源文件存储在 module-name/src/test/java/ 目录下,通常在新建完项目以后,该目录会自动创建,并且会存在一个默认的测试用例代码。
代码目录结构如下:
module-name/src
├── androidTestjava (插桩单元测试、Espresso测试)
├── main/java (业务代码)
├── test/java (本地单元测试)
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
常见API:参阅文档 JUnit annotations 和 Android annotations.
@Before: 使用这个注解可以指定一段包含测试设置操作的代码。测试类在每个测试执行前调用这段代码。你可以定义多个带有@Before注解的方法, 但是在测试类中这个方法执行的顺序是不明确的。
@After: 这个注解指定一段代码包含测试卸载操作。在每个测试方法执行后调用这段代码。你可以定义多个@After操作的代码。使用这个注解来释放内存中的资源。
@Test: 使用这个注解标识一个测试方法。 一个单独的测试类可以对应多个测试方法。
@Rule: @Rule通过复用的方法,可以允许你可以灵活的添加和修改每个测试方法。 在Android测试中, 此注解需要和Android测试支持库中提供的测试规则类配合命用。比如 ActivityTestRule和 ServiceTestRule
@BeforeClass: 使用此注解来指定一个测试类的静态方法只能调用一次。这种测试步骤对于耗时操作非常有用, 例如连接数据库操作。
@AfterClass: 使用这个注解来指定一个静态方法, 当类中所有的测试方法都已经运行完成的时候调用。 这个步骤对释放@BeforeClass块中占用的资源非常有用。
@Test(timeout=): 一些注解支持在注解中设置变量值。 例如, 你可以指定一个测试的超时时间,如果一个测试开始并且没有在指定的超时时间内完成, 它自动认为校验失败。超时时间的单位是毫秒, 如 @Test(timeout=5000)。
@Ignore:让Junit忽略某些测试方法,可能是由于测试方法没有完全编写完成或者太过于耗时。
@Test(expected = NullPointerException.class):验证测试方法会抛出某些异常,在Junit测试中通过设置expected参数指定异常类型例如NullPointerException空指针异常,如果该测试方法抛出IllegalArgumentException异常则绿色passed,如果没有抛出的话,则测试红色failed失败。
示例代码:
获取三个数中最大的那个数:这个为了说明setup的操作,没有把max方法写成static类型。
public class MathUtil {
public int max(int a, int b, int c){
if(a > b){
if(a > c){
return a;
}else{
return c;
}
}else{
if(b > c){
return b;
}else{
return c;
}
}
}
}
测试代码
测试代码简单分析:
@Before 注解的setup方法可以让测试程序处理一些初始化操作,比如公有的对象初始化比如下面的代码创建了mathUtil对象。,如果这个对象不单单是在一个测试方法用的对象话,这样可以避免在每个测试类中都编写创建该对象的逻辑。在运行测试代码的时候,首先会执行被@Before注解的方法,注意:被@Before注解的方法,不是执行一次,而是每个被@Test注解的方法执行之前都是调用setup方法。
@After注解的方法,主要用于释放资源,一般很少用到,比如文件关闭,数据库断开链接之类的。和@Before一样,每个测试方法执行完以后,也都会调用@After方法。
验证单元测试的结果,junit本身提供了一整套断言功能,但是目前推荐使用Truth库,下面用的就是Truth库中的比较两个int是否相等的逻辑。断言的逻辑比较简单,更多的API可以参考Truth库
public class MathUtilTest {
MathUtil mathUtil;
@Before
public void setup() {
mathUtil = new MathUtil();
}
@Test
public void testMax() {
Truth.assertThat(mathUtil.max(1, 2, 3)).isEqualTo(3);
}
@After
fun releaseResource(){
}
}
测试结果
参考:https://blog.csdn.net/u011060103/article/details/107297512
总结
本文主要介绍junit的常见api和Truth断言库的简单使用,整体来看junit是比较简单的,可以针对一些public方法方法做单元测试并验证单元测试的结果是否符合预期。但是我们在实际业务开发的时候往往没有这么简单,主要涉及到以下几个问题:
(1)类中存在大量的private方法,在实际业务开发中,public方法往往只是对外暴露没有实际逻辑,实际逻辑都写在private中,而junit不能直接测试private方法。
(2)对于没有返回值的方法,junit无法断言,这个时候我们有需要测试该void方法是否得到执行。
(3)如果有的方法依赖于Android环境,比如content等。
解决以上的问题,需要借助于Robolectric 或模拟框架(如Mockito库)来实现上述问题。