Android单元测试: 首先,从是什么开始
Android单元测试: 首先,从是什么开始
http://chriszou.com/2016/04/13/android-unit-testing-start-from-what.html
这是一系列安卓单元测试的文章,目测主要会cover以下的主题:
- 什么是单元测试
- 为什么要做单元测试
- JUnit
- Mockito
- Robolectric
- Dagger2
- 一个具体的app例子实践
- 神秘的bonus
什么是单元测试
首先需要介绍一下什么是单元测试。很多人像我一样,本科并不是计算机专业出身的,如果在职的公司不要求做单元测试的话,可能对这个词并没有一个确切的概念。而即使是计算机专业出身,如果毕业以后写的不多的话,可能对这个词的含义也不是很清楚。从名字上看,单元测试是为了测试某一个代码单元而写的测试代码。但是什么叫“一个代码单元”呢?是一个模块、还是一个类、还是一个方法(函数)呢?不同的人、不同的语言,都有不同的理解。一般的定义,尤其是是在OOP领域,是一个类的一个方法。在此,我们也这样理解:单元测试,是为了测试某一个类的某一个方法能否正常工作,而写的测试代码。
我们举一个例子说明一下,假如你有一个类,定义如下:
public class Calculator {
public int add(int one, int another) {
//为了简单起见,暂不考虑溢出等情况。
return one + another;
}
}
那么为了测试这个Calculator类的add()方法,我们可以写如下的单元测试代码:
public class CalculatorTest {
public void testAdd() throws Exception {
Calculator calculator = new Calculator();
int sum = calculator.add(1, 2);
Assert.assertEquals(3, sum);
}
}
这里的CalculatorTest是Calculator对应的测试类。而这里的testAdd()就是add()这个方法对应的测试方法。所以,写单元测试,就是给你的每个类的每个public方法写对于的测试方法。非public方法我们一般是不测试的,虽然可以通过反射等手段去做,但是一般看来,非public方法是这个类的实现细节,我们并不关心,我们只关心某一个public方法的输入、输出。
一般来说,一个方法对应的测试方法主要分为3部分,以上面的测试方法为例:
- setup。一般是new出你要测试的那个类,以及其他一些前提条件的设置:
Calculator calculator = new Calculator(); - 执行操作。一般是调用你要测试的那个方法,获得运行结果:
int sum = calculator.add(1, 2); - 验证结果。验证得到的结果跟预期中是一样的:
Assert.assertEquals(3, sum);
一般来说,我们写单元测试,会用到一些单元测试框架。常见的Java单元测试框架有JUnit、TestNG等等。在这个系列的文章中,我采用JUnit 4,这也是用的最多的一个测试框架。上面的第三部,Assert.assertEquals(3, sum); 用的就是JUnit里面的验证结果的方法,最常见的就是调用Assert类的一些assert方法。除了上面用到的assertEquals,还有assertTrue, assertNotNull等等。关于JUnit,我会在后面的系列文章中专门介绍。
如何在一个android project里面运行单元测试
我们知道,在一个android gradle project中,源代码默认是放在src/main/java下面的。而对应的单元测试代码则是放在src/test/java下面的,如下图所示:
其中的package name可以随意定,很多人喜欢跟src package
name保持一致,我个人习惯在最后加上.test后缀,因为AndroidStudio太智能了,经常我需要重命名单元测试的package的时候,AndroidStudio会把src的package也给重命名了。
打开CalculatorTest,用鼠标右键点击testAdd()方法,选择Run testAdd(), 如下图所示:
从图中你可以看出,你可以按快捷键Ctrl+Shift+R快速运行,当然,这要求你的光标当前焦点是在这个方法内部的。如果你的焦点是在类内部,而不在某一个测试方法内部,那么Ctrl+Shift+R将运行这个测试类的所有测试方法。当然,你也可以通过右键点击测试类名来运行这个测试类里面的所有测试方法。
运行结束以后,你会在底部的“Run”这个tab看到运行的结果,如下图所示:
除了在AndroidStudio里面运行,你还可以在命令行通过gradle testDebugUnitTest,或者是gradle testReleaseUnitTest,分别运行debug和release版本的unit testing,这种方式可以一次性运行所有测试类的所有测试方法。 这种方式的运行结果如下图所示:
每个test case的报告可以在project_root/app/build/reports/tests/debug/index.html 这个xml里面看到。大致如下图:
单元测试不是集成测试
这里需要强调一个观念,那就是单元测试只是测试一个方法单元,它不是测试一整个流程。举个例子来说,一个Login页面,上面有两个输入框和一个button。两个输入框分别用于输入用户名和密码。点击button以后,有一个UserManager会去执行performlogin操作,然后将结果返回,更新页面。
那么我们给这个东西做单元测试的时候,不是测这一整个login流程。这种整个流程的测试:给两个输入框设置正确的用户名和密码,点击login
button,
最后页面得到更新。叫做集成测试,而不是单元测试。当然,集成测试也是有他的必要性的,然而这不是我们每个程序员应该花多少精力所在的地方。在这方面,有一个理论叫做Test Pyramid,如下图所示:
Test Pyramid理论基本大意是,单元测试是基础,是我们应该花绝大多数时间去写的部分,而集成测试等应该是冰山上面能看见的那一小部分。
为什么是这样呢?因为集成测试设置起来很麻烦,运行起来很慢,发现的bug少,在保证代码质量、改善代码设计方面更起不到任何作用,因此它的重要程度并不是那么高,也无法将它纳入我们正常的工作流程中。
而单元测试则刚好相反,它运行速度超快,能发现的bug更多,在开发时能引导更好的代码设计,在重构时能保证重构的正确性,因此它能保证我们的代码在一个比较高的质量水平上。同时因为运行速度快,我们很容易把它纳入到我们正常的开发流程中。
至于为什么集成测试发现的bug少,而单元测试发现的bug多,这里也稍作解释,因为集成测试不能测试到其中每个环节的每个方面,某一个集成测试运行正确了,不代表另一个集成测试也能运行正确。而单元测试会比较完整的测试每个单元的各种不同的状况、临界条件等等。一般来说,如果每一个环节是对的,那么在很大的概率上,整个流程就是对的。虽然不能保证整个流程100%一定是对的。所以,集成测试需要有,但应该是少量,单元测试是我们应该花重点去做的事情。
那对于这个例子,单元测试是怎么样的呢?这个请看下一小节。
两种函数(方法),两种不同的测试方式
一个类的方法可以分为两种,一种是有返回值的,另一种是没有返回值的。对于有返回值的方法,我们要测起来比较容易,就跟上面的Calculator例子那样,输入相应的参数,得到相应的返回值,然后验证得到的返回值跟我们预期的值一样,就好了。
但是没有返回值的方法,要怎么测试呢?比如说刚刚login的例子,点击那个按钮,会执行Activity的login()方法,它的定义如下:
public void login() {
String username = ...//get username from username EditText
String password = ...//get password from password EditText
//do other operation like validation, etc
...
mUserManager.performlogin(username, password);
}
这个方法是void的,那么怎么验证这个方法是正确的呢?其实仔细想想,这个方法也是有输出的,它的输出就是,调用了mUserManager的performLogin方法,同时传给他两个参数。所以只要验证mUserManager的performLogin方法得到了调用,同时传给他的参数是正确的,就说明这个方法是能正常工作的。
那怎么样验证这个Activity的login()方法,会调用mUserManager的performLogin方法呢?这里涉及到mock的概念,在后面的文章关于Mockito的使用的时候,会介绍到,这里简单解释下,那就是在测试环境下,我们会使用一套mock framework(Mockito),生成一个mock的UserManager然后赋值给mUserManager,因为这个mUserManager是通过mock framework生成的,所以这个mock framework可以验证它的什么方法被调用了,参数是什么,等等。
小结
上面讲述了单元测试的定义,以及与集成测试的区别,一般来说,单元测试不会接触到数据库,不会接触到网络,不会接触到一些复杂的外部环境,如果有的话,那可能是你测试的方式有误,测试的粒度不够“单元”,希望这篇文章能将这两者的区别解释清楚。
这篇文章的代码在github上
最后,如果你对安卓单元测试感兴趣,欢迎加入我们的交流群,因为群成员超过100人,没办法扫码加入,请关注下方公众号获取加入方法。
如果文中有任何的错误或疑问欢迎留言。
Android单元测试: 首先,从是什么开始的更多相关文章
- Android随笔之——Android单元测试
在实际开发中,开发android软件的过程需要不断地进行测试.所以掌握Android的单元测试是极其重要的.您应该把单元测试作为Android应用开发周期的一部分,精心编写的测试可以在开发早起帮你发现 ...
- Android单元测试实践
为什么要写单元测试 首先要介绍为什么蘑菇街支付金融这边会采用单元测试的实践.说起来比较巧,刚开始的时候,只是我一个人会写单元测试.后来老板们知道了,觉得这是件 很有价值的事情,于是就叫我负责我们组的单 ...
- Android单元测试
安卓单元测试总结文章,目测主要会cover以下的主题: 什么是单元测试 为什么要做单元测试 JUnit Mockito Robolectric Dagger2 一个具体的app例子实践 神秘的bonu ...
- Android单元测试之四:仪器化测试
Android单元测试之四:仪器化测试 仪器化测试 在某些情况下,虽然可以通过模拟的手段来隔离 Android 依赖,但代价很大,这种情况下可以考虑仪器化的单元测试,有助于减少编写和维护模拟代码所需的 ...
- Android单元测试之三:使用模拟框架模拟依赖
Android单元测试之三:使用模拟框架模拟依赖 基本描述 如果是一些工具类方法的测试,如计算两数之和的方法,本地 JVM 虚拟机就能提供足够的运行环境,但如果要测试的单元依赖了 Android 框架 ...
- Android单元测试之一:基本概念
Android单元测试之一:基本概念 简单介绍 单元测试是应用程序测试策略中的基本测试,通过对代码进行单元测试,一方面可以轻松地验证单个单元的逻辑是否正确,另一方面在每次构建之后运行单元测试,可以快读 ...
- android单元测试 activity跳转 以及 input 输入后 测试
Android junit实现多个Activity跳转测试 分类: Android Junit测试2011-11-14 16:49 1601人阅读 评论(2) 收藏 举报 androidjunitla ...
- Android 单元测试学习计划
网上查了一下Android单元测试相关的知识点,总结了一个学习步骤: 1. 什么是单元测试2. 单元测试正反面: 2.1. 重要性 2.2. 缺陷 2.3. 策略3. 单元测试的基础知识: 3.1. ...
- 阿里知识储备之二——junit学习以及android单元测试
一,junit框架 http://blog.csdn.net/afeilxc/article/details/6218908 详细见这篇博客 juit目前已经可以和maven项目进行集成和测试,而且貌 ...
随机推荐
- tomcat端口占用后的解决办法
学 习网页设计的同学都会用到tomcat这个软件,在安装的时候我们一般都会选择端口为8080端口,这个端口一般情况下是不会有程序占用的,所以我们运行 tomcat不会出现什么问题,但是如果一旦别占用, ...
- 复合命令A等效于$a
时间:2014.06.28 地点:基地 ------------------------------------------------------------------------------- ...
- Python函数对象
秉承着一切皆对象的理念,我们再次回头来看函数(function).函数也是一个对象,具有属性(可以使用dir()查询).作为对象,它还可以赋值给其它对象名,或者作为参数传递. lambda函数 在展开 ...
- 文件操作2 cp mv rm
1.cp命令 [root@rusky /]# cp 123 /test #在linux系统中,如果文件123已经存在,则提示用户确认,在unix系统中则不提示,除非使用参数-i 交互式操作.cp: ...
- Find命令简介
Find命令主要用于目标的搜索,尽量做到少使用,因为find会消耗大量的系统资源. 使用该命令时,需要避开服务器运行高峰期,最好在指定的小范围内进行搜索,不要轻易使用全盘搜索. Find命令常用的参数 ...
- Java 8十个lambda表达式案例
1. 实现Runnable线程案例 使用() -> {} 替代匿名类: //Before Java 8: new Thread(new Runnable() { @Override public ...
- Tabs( 选项卡)
一. 加载方式 //class 加载方式<div id="box" class="easyui-tabs" style="width:500px ...
- JavaScript绑定事件的方法[3种]
在JavaScript中,有三种常用的绑定事件的方法: 在DOM元素中直接绑定: 在JavaScript代码中绑定: 绑定事件监听函数. 一. 在DOM元素中直接绑定 这里的DOM元素,可以理解为HT ...
- python 下的数据结构与算法---5:递归(Recursion)
定义:递归就是不断分割整体成部分直到可以轻易解决分割出来的部分. 递归表达式三定律: 1:递归表达式必须有个最小单元 (最小单元既是停止递归调用以及能够直接运算的) 2:递归表达式在运算过程中 ...
- 2:numpy---ndarray
ndarray即是多维数组[n dimension array] 一:创建ndarray 有好几种创建数组的方法. 例如,你可以使用 array 函数从常规的Python列表和元组创造数组.所创建的数 ...