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

这些方式有几个缺点:

  • 测试不完整,挖有一些隐藏的坑
  • 改代码测试,在该回来的时候可能引入新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. copy-and-swap idiom

    This answer is from https://stackoverflow.com/a/3279550/10133369 Overview Why do we need the copy-an ...

  2. java web工程的配置文件

    java web工程的配置文件 1.工程(源码依赖管理) 2.代码生成管理: 3.会话管理:servlet: 4.应用管理: 5.(分布式)资源管理:数据.数据库连接等. pom:源码管理工具 位置: ...

  3. git基本操作:分支管理

    一.创建测试项目 1.新建GitHub仓库 在GitHub上面新创建一个仓库,用来演示分支管理,如下图所示: 点击“Create repository”按钮创建新仓库. 2.将本地仓库项目上传到Git ...

  4. Python超详细的字符串用法大全

    字符串拼接 实际场景:把列表中的数据拼接成一个字符串 解决方案:使用 str.join() 方法 >>> li = ['cxk', 'cxk', 'kk', 'caibi'] > ...

  5. Python中最常用的字符串方法!

    字符串是字符序列.Python中内置的string类代表基于Unicode国际字符集的字符串.除了Python中常见的操作外,字符串还有一些专属于它们的附加方法.下图显示了所有这些可用的方法: Pyt ...

  6. JavaScript 数学

    JavaScript Math 数学 神奇的圆周率 Math.PI ; // 返回 3.1415926535-- Math 数学方法 Math.round() Math.round(X):返回 X 的 ...

  7. 实时数据推送webSocket

    实时数据推送 在Web或移动项目中,服务器向客户端实时推送消息是一种常见的业务需求. 实现方式 Polling:轮询(俗称“拉”),即定期重新请求数据. Long-Polling:长轮询,是 Poll ...

  8. [20190515]热备份模式与rman冲突.txt

    [20190515]热备份模式与rman冲突.txt --//别人的系统做dg时打开热备份模式,忘记关闭,做rman备份时报错.做一个记录.--//实际上也怪自己,实施时没有讲清楚.通过例子说明: 1 ...

  9. python踩坑系列之导入包时下划红线及报错“No module named”问题

    python踩坑系列之导入包时下划红线及报错“No module named”问题 使用pycharm编写Python时,自己写了一个包(commontool),在同级另一个路径下(fileshand ...

  10. 标签中href="javascript:;"表示什么意思?

    1.标签的 href 属性用于指定超链接目标的 URL,href 属性的值可以是任何有效文档的相对或绝对 URL,包括片段标识符和 JavaScript 代码段. 2.javascript: 是一个伪 ...