Java单元测试简述
最开始项目中是没有单元测试的,基本都是自己通过各种方式来实现测试的。比如修改代码,测完再改回来;再比如直接模拟用户操作,直接当黑盒测试,然后自己去看相应的逻辑有没有,状态有没有改变。
这些方式有几个缺点:
- 测试不完整,挖有一些隐藏的坑
- 改代码测试,在该回来的时候可能引入新bug
- 手工测试比较耗时
- 下次改需求时,需要再次手工测试
这个里面多次手工测试比较难受,太浪费时间了。以前由于一个逻辑牵扯比较多,构造对象比较复杂,仅仅用JUnit写测试的工作量还是太大,所以单元测试一直没有进行下去。
后来引入的mockito框架来用于新代码的测试,powermock用于以前的代码测试。下面将介绍一下mockito和powermock框架,就明白为什么要用这两个框架了。
Mockito
mockito是用的比较广的mock框架。mock技术的目的和作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开。
为了说明使用方法,先引入一下基本对象
public class User {
private int userId;
private ComplexObject complexObject;
public int getUserId() {
return userId;
}
//... construction getter
}
public class Service {
public boolean checkUser(User user) {
if(user.getUserId() < 100){
return true;
}
return false;
}
}
默认的static import
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
Mock
如果要测试Service#checkUser方法,我们就要构造User对象。假设ComplexObject构造很复杂,如果不用mock,测试将寸步难行。下面来看看mockito是如何构造一个假的User并进行测试的吧。
@Test
public void testCheckUser() throws Exception {
Service service = new Service();
User user = mock(User.class);
when(user.getUserId()).thenReturn(2);
boolean checkResult = service.checkUser(user);
assertTrue(checkResult);
}
上面可以看到只用mock方法就可以了,然后设置一下getUserId方法的返回就行了。when的语法理解很容易,就不解释了。
上面的when语句也可以换成
doReturn(2).when(user).getUserId();
在这个例子中,这两种when的写法都是可行的。
一共有以下几种方式来模拟一个方法。
- doReturn
- doCallRealMethod
- doNothing
- doThrow
- doAnswer
当然也有thenXXX这种形式。
Spy
spy和mock很像,都是模拟一个对象。但是mock是把所有方法都接管了,spy是默认调用对象的方法。如果先mock出一个对象,然后对每一个方法调用doCallRealMethod,这就相当于spy出一个对象。
所以spy和mock只是初始模拟对象的默认设置不一样而已,其他行为都是一样的。
Annotation
可以直接用注解来实现mock:
@Mock
User user;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testCheckUser() throws Exception {
Service service = new Service();
doReturn(2).when(user).getUserId();
boolean checkResult = service.checkUser(user);
assertTrue(checkResult);
}
这个需要调用initMocks(this)来注入,这里是通过@Before,也可以通过@RunWith来调用initMocks方法。
也可以用Spy注解:
@Spy User user;
还有一个注解比较有用@InjectMocks,这个可以把对象注入到其他对象中去的。
下面稍微添加一下代码:
public class ComplexObject {
@Override
public String toString() {
return "Complex lhcpig";
}
//...
}
public class Service {
public String handleUser(User user){
return user.getComplexObject() + "";
}
//...
}
public class TestService {
@InjectMocks
User user;
@Spy
ComplexObject complexObject;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testHandleUser() throws Exception {
Service service = new Service();
String s = service.handleUser(user);
assertThat(s, is("Complex lhcpig"));
}
//...
}
- 注1:这里和之前的那个test有冲突,因为User的注解不一样,所以第一个test会报NotAMockException或者MissingMethodInvocationException异常。
- 注2:这里用Spy,可以不用额外代码,就CallRealMethod。
Verify
这个是用来判断方法是否被调用,调用是否超时,调用了多少次等测试。
@Test
public void testCheckUser() throws Exception {
Service service = new Service();
when(user.getUserId()).thenReturn(2);
boolean checkResult = service.checkUser(user);
assertTrue(checkResult);
verify(user).getUserId();
verify(user, timeout(100)).getUserId();
user.getUserId();
verify(user, times(2)).getUserId();
}
如果方法有参数,也可以验证参数。
这里只是简介,如果想详细了解Mockito,建议还是看官网文档。
PowerMock
Mockito不支持final方法,私有方法,静态方法,而PowerMock支持。所以这里也要介绍一下。但是还是不建议项目中使用,如果需要使用PowerMock才能测试,说明代码的可测试性不好,需要改进代码。一般都是历史遗留代码或者第三方库相关测试的时候才需要使用。
下面是使用方式
@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClassWithEgStaticMethod.class })
public class YourTestCase {
...
}
给个例子,大家就理解了
@RunWith(PowerMockRunner.class)
@PrepareForTest( { Service.class })
public class TestService {
@Before
public void initMocks() {
mockStatic(Service.class);
}
@Test
public void testTestStaticFinal() throws Exception {
PowerMockito.when(Service.testStaticFinal()).thenReturn("mock1");
assertEquals("mock1", Service.testStaticFinal());
}
@Test
public void testPrivate() throws Exception {
Service t = mock(Service.class);
PowerMockito.when(t, "testPrivate").thenReturn("xxx");
doCallRealMethod().when(t).testPrivateForPublic();
assertEquals("xxx", t.testPrivateForPublic());
}
@Test
public void testTestPrivateWithArg() throws Exception {
Service t = spy(new Service());
String arg = "dd";
PowerMockito.when(t, "testPrivateWithArg", arg).thenReturn("lhc");
assertEquals("lhc", t.getTestPrivateWithArg(arg));
}
}
public class Service {
public static final String testStaticFinal() {
System.out.println("testStaticFinal");
return "static final";
}
private String testPrivate() {
System.out.println("testPrivate");
return "private";
}
public String testPrivateForPublic() {
System.out.println("testPrivateForPublic");
return testPrivate();
}
private String testPrivateWithArg(String arg) {
System.out.println("testPrivateWithArg");
return arg + "x";
}
}
私有方法用PowerMock测试后,如果要修改名字就会很麻烦,重构起来也可能会影响测试用例。所PowerMock的正确使用方式是尽量不使用。
因为要反射调用私有方法,所以写法没有mockito那么优雅。我这里使用的是基于Mockito的PowerMock,所以可以混合使用,比如上面用到的spy,when等。当然PowerMock还有基于其他mock框架(EasyMock)的扩展,这里就不再进一步介绍了。
想让测试更加高效,测试框架还是其次,写出可测试性的代码才是最重要的。
Java单元测试简述的更多相关文章
- Java单元测试技术1
另外两篇关于介绍easemock的文章:EasyMock 使用方法与原理剖析,使用 EasyMock 更轻松地进行测试 摘要:本文针对当前业软开发现状,先分析了WEB开发的技术特点和单元测试要解决的问 ...
- 转载-使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试
JUnit 是被广泛应用的 Java 单元测试框架,但是它没有很好的提供参数化测试的支持,很多测试人员不得不把测试数据写在程序里或者通过其它方法实现数据与代码的分离,在后续的修改和维护上有诸多限制和不 ...
- Java单元测试工具:JUnit4(一)(二)(三)(四)
Java单元测试工具:JUnit4(一)--概述及简单例子 Java单元测试工具:JUnit4(二)--JUnit使用详解 Java单元测试工具:JUnit4(三)--JUnit详解之运行流程及常用注 ...
- Java单元测试(Junit+Mock+代码覆盖率)
微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...
- Java单元测试框架 JUnit
Java单元测试框架 JUnit JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于KentBeck的sUnit的xUnit家族中为最成功的一 ...
- Maven的安装配置及初次创建项目与java单元测试工具JUnit
Maven 安装 1.把maven安装包解压到某个位置 2.配置M2_HOME环境变量指向这个位置 3.在path环境变量中添加;%M2_HOME%\bin 配置镜像 国内的阿里云镜 ...
- 原!!关于java 单元测试Junit4和Mock的一些总结
最近项目有在写java代码的单元测试,然后在思考一个问题,为什么要写单元测试??单元测试写了有什么用??百度了一圈,如下: 软件质量最简单.最有效的保证: 是目标代码最清晰.最有效的文档: 可以优化目 ...
- 有效使用Mock编写java单元测试
Java单元测试对于开发人员质量保证至关重要,尤其当面对一团乱码的遗留代码时,没有高覆盖率的单元测试做保障,没人敢轻易对代码进行重构.然而单元测试的编写也不是一件容易的事情,除非使用TDD方式,否则编 ...
- Java单元测试(Junit+Mock+代码覆盖率)---------转
Java单元测试(Junit+Mock+代码覆盖率) 原文见此处 单元测试是编写测试代码,用来检测特定的.明确的.细颗粒的功能.单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的. 单元测 ...
随机推荐
- Do Deep Nets Really Need to be Deep?
url: https://arxiv.org/pdf/1312.6184.pdf year: NIPS2014 浅网络学习深网络的函数表示, 训练方法就是使用深网络的 logits(softmax i ...
- rsync免交互方法
添加-e "ssh -o StrictHostKeyChecking=no" rsync -avzP -e "ssh -o StrictHostKeyChecking=n ...
- Redisson实现分布式锁(1)---原理
Redisson实现分布式锁(1)---原理 有关Redisson作为实现分布式锁,总的分3大模块来讲. 1.Redisson实现分布式锁原理 2.Redisson实现分布式锁的源码解析 3.Redi ...
- ElasticSearch简介(三)——中文分词
很多时候,我们需要在ElasticSearch中启用中文分词,本文这里简单的介绍一下方法.首先安装中文分词插件.这里使用的是 ik,也可以考虑其他插件(比如 smartcn). $ ./bin/ela ...
- 新增SAP到OA接口,OA怎么更新WSDL给PI,怎么选择PI的IP地址(备忘)
1.首先定义个class. 弄完以后可以使用http://IP地址:8088/seeyon/services/sapService?wsdl 进行导出,部署在哪个服务器就用哪个服务器的IP地址 pr ...
- DataGridView中的rows.Count比实际行数多1的原因以及解决办法
场景 DataGridView怎样实现添加.删除.上移.下移一行: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10281414 ...
- 函数的防抖---js
执行规定一段时间后执行 <input type="text" id="inp" /> <script> var oInp = docum ...
- android ANR 分析定位问题
ANR ? android 规定,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件,BroadcastReceiver 如果10s中之内还未执行完操作就会出现ANR 定位ANR问题 ...
- 码云 git 常用命令
git 常用命令: git init 创建一个本地文件 ①git add . 当前文件夹下的所有内容 添加到暂存区 git add test.txt 指定文件夹添加 可以添压缩文件 ②git comm ...
- 江苏OSS用户权限修改
市场服务二部”修改为“市场二部”.“市场服务三部”修改为“市场三部”.“县域服务一部”修改为“县域一部”.“县域服务二部”修改为“县域二部”.“综合管理部”修改为“综合业务部”. SELECT * ...