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

这些方式有几个缺点:

  • 测试不完整,挖有一些隐藏的坑
  • 改代码测试,在该回来的时候可能引入新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. python 实例方法、静态方法、类方法

    class Date: #构造函数 def __init__(self, year, month, day): self.year = year self.month = month self.day ...

  2. eclipse强行停止buliding workspace

    使用Eclipse的过程中可能会遇到buliding workspace卡在一半走不动的情况. 出现这个情况往往是因为Eclipse太调皮了,需要拉出去打屁股,打一顿就好了. 开玩笑的,事实上出现这个 ...

  3. 一篇短文带您了解一下EasyCaching

    前言 从2017年11月11号在Github创建EasyCaching这个仓库,到现在也已经将近一年半的时间了,基本都是在下班之后和假期在完善这个项目. 由于EasyCaching目前只有英文的文档托 ...

  4. java.lang.ClassNotFoundException: XXX (no security manager: RMI class loader disabled)

    在搞RMI远程发布,consumer去获取rmi远程服务的代理对象的时候出现了如下的错误 问题发现: 由于我发布的对象的包路径和获取的对象的包路径不一致,导致了这样的问题 解决方案: 包路径改为一致就 ...

  5. 练手WPF(三)——扫雷小游戏的简易实现(下)

    十四.响应鼠标点击事件    (1)设置对应坐标位置为相应的前景状态 /// <summary> /// 设置单元格图样 /// </summary> /// <para ...

  6. SQL SERVER 数据库授权指定用户

    在查询分析器下运行以下语句即可: GO USE [master] GO ALTER AUTHORIZATION ON DATABASE::[数据库名] TO [用户名] GO

  7. Java生鲜电商平台-促销系统的架构设计与源码解析

    Java生鲜电商平台-促销系统的架构设计与源码解析 说明:本文重点讲解现在流行的促销方案以及源码解析,让大家对促销,纳新有一个深入的了解与学习过程. 促销系统是电商系统另外一个比较大,也是比较复杂的系 ...

  8. 用BAPI_ACC_DOCUMENT_POST过账生成凭证

    根据前台需要输入参数,在bapi里面传值,不同业务所需参数不同. dome1: "bapi结构赋值     LOOP AT lt_item INTO lw_item.       wa_do ...

  9. Tomcat启动分析(二)-自己编译Tomcat

    为了方便分析Tomcat源码,利用大家习惯的方式来进行Debug调试,那么如何将Tomcat源码导入到Eclipse呢,这就是本文的重点 1 准备 1.1 获取Tomcat源码 获取tomcat源码有 ...

  10. vue中如何使用echarts,使用axios获取数据

    1==>首先准备一个容器 <div id="echartContainer" style="width:400px; height:400px"&g ...