单元测试是什么
首先我们来介绍一下什么是单元测试?可能有很多人经常会听到这个词并不感到陌生,那什么是单元测试呢,在Android中又是如何实践的呢,这个时候可能会感到困惑。从名字上看,单元测试就是参与项目开发的工程师在项目中为了测试某一个代码单元而写的测试代码,用于执行项目中的目标函数并验证其逻辑状态或者结果。。这里提到的“一个代码单元”指的是测试的最小模块,通常指函数。这些代码是白盒测试,能够检测目标代码的准确性和可靠性,在打包时单元测试的代码并不会被编译进入release apk中。
为什么要写单元测试
在敏捷开发开发中,Android项目版本快速迭代上线,这个往往需要除黑盒测试之外更加可靠的质量保证,这正是单元测试的用武之地。或许我们经常遇到这种情况,自己的模块代码早早写好了,但其他模块接口还未OK,在最后的紧急关头集成联调时,结果自己的模块出现很多bug,可能连代码最基本的逻辑都跑不通。这个时候就需要单元测试来提前暴露自己的问题,从而保证自己代码逻辑的准确性和可靠性。看到这里,你可以明白单元测试有多重要了:
- 对自己的代码更加有信心
自己写的代码要准备上线的时候,可能感觉会很心虚,总有一种铤而走险的感觉,甚至在上线时祈祷自己的模块没有问题,这些都是对自己写的代码没有信心的表现,毕竟空口无凭。虽然说单元测试并不能百分之百保证在集成联调时能够完全正确运行,但保证了自己的每一代码单元测试通过,可以很明显的增加对代码的信心。 - 为代码重构保驾护航
项目有多人经手,代码很乱很差劲,有时候想去重构,但是又担心重构之后出问题,那该怎么办呢?但是有了单元测试那就不一样,我们可以边重构边写单元测试,这样基本上可以保证我们的重构没有破坏原有的代码逻辑的准确性。比如我现在所在的项目是通过MVC设计模式,所以导致Activity中代码特别臃肿,比较难维护。再者通过MVP设计模式来重构项目代码,可以抽出Presenter这个纯Java层,便于写单元测试。 - 写单元测试本身就是一次小范围的代码重构
好的东西都是千锤百炼出来的,比如好的架构也不例外,都是经历作者不停的review和修改。写单元测试的过程本身也是一个对自我code 不断review的过程,在这个过程中,可以发现一些逻辑设计上的问题,代码编写方面的问题,比如一些边界条件处理、空对象的保护和类构造时参数的依赖问题,还有一些对Android Context的依赖问题。 - 节约开发时间
如果没有单元测试的话,我们coding时为了调试某个功能,看界面是否显示正确,把APP运行起来,如果有错的话,改一点东西,再运行起来。。。哇,这个过程太漫长太痛苦,特别在大型项目中,run一次需要五六分钟,这个时间太漫长了,完全在浪费时间,coding的效率也很低。有了单元测试,在开发过程中可以更少的把APP运行起来,一两分钟的等待就可以验证代码逻辑是否正确,速度相对来说快很多。此外,通过单元测试能帮我们减少bug,从而减少了调试bug和fix bug的时间 - 更快的发现bug
如果没有单元测试,我们就需要在最后的集成联调中才能发现自己模块的问题,一旦问题比较严重的话,可能会让自己感觉有压力和恐慌。但是通过单元测试,可以有多一层的保证,可以更早的发现问题和解决问题,在集成时让自己更加闲庭信步。
使用JUnit4写单元测试
JUnit4是一个Java语言的单元测试框架,由Kent Beck和Erich Gamma编写的一个回归测试框架。多数Java的开发环境都已经集成了JUnit作为单元测试的工具,也是用的最多的一个测试框架,Android Studio创建的工程中就已经集成了JUnit4,默认就加了这个dependencies,如下:
dependencies {
testCompile "junit:junit:4.12"
}
下面我们来写一个简单的单元测试,假如我们有一个这样的类,定义如下:
public class Calculator {
public int add(int one, int another) {
return one + another;
}
}
我们想测试这个Calculator类的add(int one, int another)方法,在Android studio project中,源代码默认放在src/main/java下面的,而对应的单元测试代码则是放在src/test/java目录中,定义一个CalculatorTest类,单元测试代码如下:
public class CalculatorTest {
@Test
public void testAdd() throws Exception {
Calculator calculator = new Calculator();
int sum = calculator.add(1, 2);
assertEquals(3, sum);
}
}
我们也可以通过Android Studio的快捷方式来创建单元测试类,选中目标类或者目标方法点击右键,选择GoTo--->Test来快速创建单元测试方法,方式如图:
打开CalculatorTest,鼠标右键点击testAdd()方法,选择Run testAdd(),如下图所示:
这里的CalculatorTest是Calculator对应的测试类,这里的testAdd()就是add()这个方法对应的测试方法。所以在单元测试时,就是给你目标类的public方法写对应的测试方法。
一般来说,一个方法对应的测试方法主要有三步曲,以上面的测试方法为例:
- setup,一般是new出你要测试的目标类,以及设计一些前提条件:
Calculator calculator = new Calculator();
- 执行操作,一般是调用我们要测试的那个方法,同时获得结果:
int sum = calculator.add(1, 2);
- 验证结果,通过JUnit4提供的Asset断言来验证结果是否与预期一样:
Asset.assertEquals(3, sum);
在上面的例子中,你是否注意到一个@Test注解,通过声明这个注解,告诉JUnit我们要测的方法。JUnit4使用Java5中的注解(annotation),以下是JUnit4常用的几个annotation:
@Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)
@After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)
@Test:测试方法,在这里可以测试期望异常和超时时间
@Test(expected=ArithmeticException.class)检查被测方法是否抛出ArithmeticException异常
@Ignore:忽略的测试方法
@BeforeClass:针对所有测试,只执行一次,且必须为static void
@AfterClass:针对所有测试,只执行一次,且必须为static void
一个JUnit4的单元测试用例执行顺序为:
@BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
每一个测试方法的调用顺序为:
@Before -> @Test -> @After;
小结
单元测试不是集成测试,单元测试只是测试一个方法单元,不是测试一整个流程。集成测试是一种end to end的系统测试,测试相关模块集成在一起是否能够按照预期工作,一般都是接口或者功能层面的测试,可能会依赖很多系统因素,测试的代码逻辑一般比较复杂,运行时间会比较长,出错之后的修复成本高。
单元测试的目标函数主要有三种:
- 有明确的返回值,如上图的add(int one, int another),做单元测试时,只需调用这个函数,验证其返回值是否符合预期结果。
- 这个函数只改变其对象内部的一些属性或者状态,函数本身没有返回值,就验证它所改变的属性和状态。
- 一些函数没有返回值,也没有直接改变哪个值的状态,这就需要验证其行为,比如点击事件。
感谢您对本篇博客的关注,有什么不足请指正!要是想深入了解单元测试,了解Mockito测试框架,请关注博客Android单元测试之Mockito!