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+代码覆盖率) 原文见此处 单元测试是编写测试代码,用来检测特定的.明确的.细颗粒的功能.单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的. 单元测 ...
随机推荐
- centos7下mysql5.7的安装与配置
centos7下MySQL5.7的安装与配置 下载 下载地址 根据系统和版本选择红框中的四个RPM包下载即可,然后放到centos7系统中的/opt目录下,等待稍后安装. 安装前的准备 1. 检查系统 ...
- redis安装等其他操作
重启:./redis-server或者 ./redis-server redis.conf ps -ef|grep redis 得到了进程号 xxxx 然后 ls -l /proc/xxxx/cwd ...
- JavaFx出现错误Caused by: java.lang.NullPointerException: Location is required的解决方法
问题截图: "C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" "-javaagent:I:\IntelliJ IDEA ...
- wpf 两个自定义控件
wpf 两个自定义控件 一个是IP控件,一个滑动条.先看下效果图 IPControl 1.实际工作中有时需要设置IP信息,就想着做一个ip控件.效果没有window自带的好,需要通过tab切换.但也能 ...
- asp.net core 新建area使用asp-action,asp-controller不管用
解决方法: 在新建的Area目录下,这里使用Admin,Admin/Views下新建_ViewImports.cshtml和_ViewStart.cshtml两个视图文件,复制项目自动生成的到对应的新 ...
- LooseVersion()使用及.__version__版本号的获取
我简单看了distutils库,但发现目前还用不到,感觉有些复杂.因此我简单复制了别人的介绍,如下: Distutils可以用来在Python环境中构建和安装额外的模块.新的模块可以是纯Python的 ...
- fwrite(): send of 8192 bytes failed with errno=104 Connection reset by peer
问题:fwrite(): send of 8192 bytes failed with errno=104 Connection reset by peer 问题描述 通过mysql + sphinx ...
- Java性能 -- Lock优化
Lock / synchronized Lock锁的基本操作是通过乐观锁实现的,由于Lock锁也会在阻塞时被挂起,依然属于悲观锁 synchronized Lock 实现方式 JVM层实现 Jav ...
- laravel 数据库操作之 DB facade & 查询构造器 & Eloquent ORM
<?php namespace App\Http\Controllers; use App\Student; use Illuminate\Support\Facades\DB; class S ...
- [20190524]sqlplus 与输出&.txt
[20190524]sqlplus 与输出&.txt --//在sqlplus下 &一般作为参数替换,如何要输出&,一般有几种情况.据说这个问题是asktom站点查看最多的问题 ...