最开始项目中是没有单元测试的,基本都是自己通过各种方式来实现测试的。比如修改代码,测完再改回来;再比如直接模拟用户操作,直接当黑盒测试,然后自己去看相应的逻辑有没有,状态有没有改变。

这些方式有几个缺点:

  • 测试不完整,挖有一些隐藏的坑
  • 改代码测试,在该回来的时候可能引入新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单元测试简述的更多相关文章

  1. Java单元测试技术1

    另外两篇关于介绍easemock的文章:EasyMock 使用方法与原理剖析,使用 EasyMock 更轻松地进行测试 摘要:本文针对当前业软开发现状,先分析了WEB开发的技术特点和单元测试要解决的问 ...

  2. 转载-使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试

    JUnit 是被广泛应用的 Java 单元测试框架,但是它没有很好的提供参数化测试的支持,很多测试人员不得不把测试数据写在程序里或者通过其它方法实现数据与代码的分离,在后续的修改和维护上有诸多限制和不 ...

  3. Java单元测试工具:JUnit4(一)(二)(三)(四)

    Java单元测试工具:JUnit4(一)--概述及简单例子 Java单元测试工具:JUnit4(二)--JUnit使用详解 Java单元测试工具:JUnit4(三)--JUnit详解之运行流程及常用注 ...

  4. Java单元测试(Junit+Mock+代码覆盖率)

    微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...

  5. Java单元测试框架 JUnit

    Java单元测试框架 JUnit JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于KentBeck的sUnit的xUnit家族中为最成功的一 ...

  6. Maven的安装配置及初次创建项目与java单元测试工具JUnit

    Maven  安装     1.把maven安装包解压到某个位置     2.配置M2_HOME环境变量指向这个位置 3.在path环境变量中添加;%M2_HOME%\bin 配置镜像 国内的阿里云镜 ...

  7. 原!!关于java 单元测试Junit4和Mock的一些总结

    最近项目有在写java代码的单元测试,然后在思考一个问题,为什么要写单元测试??单元测试写了有什么用??百度了一圈,如下: 软件质量最简单.最有效的保证: 是目标代码最清晰.最有效的文档: 可以优化目 ...

  8. 有效使用Mock编写java单元测试

    Java单元测试对于开发人员质量保证至关重要,尤其当面对一团乱码的遗留代码时,没有高覆盖率的单元测试做保障,没人敢轻易对代码进行重构.然而单元测试的编写也不是一件容易的事情,除非使用TDD方式,否则编 ...

  9. Java单元测试(Junit+Mock+代码覆盖率)---------转

    Java单元测试(Junit+Mock+代码覆盖率) 原文见此处 单元测试是编写测试代码,用来检测特定的.明确的.细颗粒的功能.单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的. 单元测 ...

随机推荐

  1. ASP.NET(1)

    1.IIS安装问题,先装VS再装IIS,处理程序映射有问题,使用VS自带的控制台输入命令,注册路径 2.开发模式,一般处理程序,使用IO操作读取html文件,使前后端分离 3.post请求和get请求 ...

  2. Web前端基础(6):CSS(三)

    1. 定位 定位有三种:相对定位.绝对定位.固定定位 1.1 相对定位 现象和使用: 1.如果对当前元素仅仅设置了相对定位,那么与标准流的盒子什么区别. 2.设置相对定位之后,我们才可以使用四个方向的 ...

  3. HTML5新标签与特性---多媒体

    多媒体标签 embed:标签定义嵌入的内容 audio:播放音频 video:播放视频 多媒体 embed(会使用) embed可以用来插入各种多媒体,格式可以是 Midi.Wav.AIFF.AU.M ...

  4. iOS 应用程序启动时要做什么

    当您的应用程序启动(无论是在前台或后台),使用您的应用程序委托application:willFinishLaunchingWithOptions:和application:didFinishLaun ...

  5. 一文解读VR/AR/MR (转)

    1.VR=虚拟世界 VR,英文是Virtual Reality,中文是虚拟现实. VR眼镜 再细节一点来说,就是模拟一个三维的虚拟世界,在这个虚拟世界中,人们可以感受到视觉.听觉.触觉等方面的刺激. ...

  6. linux环境下安装selenium+chrom+chromdriver.exe

    原文:https://blog.csdn.net/yoyocat915/article/details/80580066 原文:https://blog.csdn.net/hanxue6898/art ...

  7. Hive性能调优(二)----数据倾斜

    Hive在分布式运行的时候最害怕的是数据倾斜,这是由于分布式系统的特性决定的,因为分布式系统之所以很快是由于作业平均分配给了不同的节点,不同节点同心协力,从而达到更快处理完作业的目的. Hive中数据 ...

  8. MySQL 主从复制开启 GTID

    GTID (Golobal Transaction ID) 是对于一个已提交事务的唯一编号,并且是一个全局(主从复制)唯一的编号. GTID 复制和传统复制的区别:在启动主从复制时,不需要指定 bin ...

  9. 海思Hi3519A MPP从入门到精通(一 系统概述)

    1. 概述 海思提供的媒体处理软件平台(Media Process Platform,简称 MPP),可支持应用软件快速 开发.该平台对应用软件屏蔽了芯片相关的复杂的底层处理,并对应用软件直接提供 M ...

  10. mysql connector c++ 1.1 安装

    mysql connector c++ 1.1 安装 前提:boost安装完毕 注意:8.0版本的源码用此方法,编译不过.所以要编译8.0的同学请绕行. 1,下载源码 git clone https: ...