Java单元测试对于开发人员质量保证至关重要,尤其当面对一团乱码的遗留代码时,没有高覆盖率的单元测试做保障,没人敢轻易对代码进行重构。然而单元测试的编写也不是一件容易的事情,除非使用TDD方式,否则编写出容易测试的代码不但对开发人员的设计编码要求很高,而且代码中的各种依赖也常常为单元测试带来无穷无尽的障碍。

令人欣慰的是开源社区各种优秀的Mock框架让单元测试不再复杂,本文简单介绍EasyMock,PowerMock等的基本常用用法。

Mock说白了就是打桩(Stub)或则模拟,当你调用一个不好在测试中创建的对象时,Mock框架为你模拟一个和真实对象类似的替身来完成相应的行为。

EasyMock:

使用如下方式在Maven中添加EasyMock的依赖:

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.2</version>
<scope>test</scope>
</dependency>

EasyMock使用动态代理实现模拟对象创建,其基本步骤为以下四步:
以数据库应用为例的被测试代码如下:

public class UserServiceImpl{
private UserDao dao;
public User query(String id) throws Exception{
try{
return dao.getById(id);
}catch(Exception e){
throw e;
}
return null;
}
}

public class UserDao{
public User getById(String id) throws Exception{
try{
return ……;
}catch(Exception e){
throw e;
}
return null;
}
}
现在希望对UserServiceImpl进行测试,而UserDao开发组只给出接口,尚未完成功能实现。
使用Mock对UserDao进行模拟来测试UserServiceImpl。

(1).基本的测试代码如下:

public class UserServiceImplTest {
@Test
public void testQuery() {
User expectedUser = new User();
user.setId(“1001”);
UserDao mock = EasyMock.createMock(UserDao.class);//创建Mock对象
Easymock.expect(mock.getById("1001")).andReturn(expectedUser);//录制Mock对象预期行为
Easymock.replay(mock);//重放Mock对象,测试时以录制的对象预期行为代替真实对象的行为

UserServiceImpl service = new UserServiceImpl();
service.setUserDao(mock);
user user = service.query("1001");//调用测试方法
assertEquals(expectedUser, user); //断言测试结果
Easymock.verify(mock);//验证Mock对象被调用
}
}
注意:

在EasyMock3.0之前,org.easymock.EasyMock使用JDK的动态代理实现Mock对象创建,因此只能针对接口进行Mock,org.easymock.classextension.EasyMock使用CGLIB动态代理创建Mock对象,可以针对普通类进行Mock。

在EasyMock3.0之后,org.easymock.classextension.EasyMock被废弃,使用org.easymock.EasyMock可以针对接口和普通类进行Mock对象创建。

(2).调用测试设定:

如果想测试UserServiceImpl调用了UserDao的getById方法3次,则使用如下代码即可:
Easymock.expect(mock.getById("1001")).andReturn(exceptUser).times(3);
(3).方法异常:

如果想测试UserServiceImpl在调用UserDao的getById方法时发生异常,可以使用如下代码:
Easymock.expect(mock.getById("1001")).andThrow(new RuntimeException());
在测试UserServiceImpl时就可以使用try-catch捕获Mock的异常。
(4).基本参数匹配:

上面的方法在Mock UserDao的getById方法时传入了“0001”的预期值,这种方式是精确参数匹配,如果UserServiceImpl在调用是传入的参数不是“0001”就会发生Unexpect method的Mock异常,可以使用下面的方法在Mock时进行参数匹配:

Easymock.expect(mock.getById(Easymock.isA(String.class))).andReturn(exceptedUser).times(3);
isA()方法会使用instanceof进行参数类型匹配,类似的方法还有anyInt(),anyObject(), isNull(),same(), startsWith()......

(5).数组类型参数匹配:

如果UserServiceImpl在调用UserDao的方法时传入的参数是数组,代码如下:

public class UserServiceImpl{
private UserDao dao;
public List<String> queryNames(String[] ids) throws Exception{
try{
return dao.getNames(ids);
}catch(Exception e){
throw e;
}
return null;
}
}

public class UserDao{
public List<String> getNames(String[] ids) throws Exception{
try{
return ……;
}catch(Exception e){
throw e;
}
return null;
}
}
此时有两种办法来进行参数匹配:
a.数组必须和测试给定的一致:

Easymock.expect(mock.getNames(EasyMock.aryEq(testIds))).andReturn(exceptedNames);
b.不考虑测试数组内容:
Easymock.expect(mock.getNames(EasyMock.isA(String[].class))).andReturn(exceptedNames);
(6).void方法Mock:
如果要Mock的方法是无返回值类型,例子如下:

public class UserDao {
public void updateUserById(String id) throws Exception{
try{
update…
}catch(Exception e){
throw e;
}
}
}
a.正常Mock代码如下:
mock.updateUserById(“TestId”);
EasyMock.expectLastCall().anytimes();
b.模拟发生异常的Mock代码如下:
mock.updateUserById(“TestId”);
EasyMock.expectLastCall().andThrow(new RuntimeException()).anytimes();
(7).多次调用返回不同值的Mock:
对于迭代器类型的遍历代码来说,需要在不同调用时间返回不同的结果,以JDBC结果集为例代码如下:

public List<String> getUserNames () throws Exception{
List<String> usernames = new ArrayList<String>();
ResultSet rs = pstmt.executeQuery(query);
try {
while(rs.next()){
usernames.add(rs.getString(2));
}
} catch (SQLException e) {
throw e;
}
}
在Mock结果集的next方法时如果总返回true,则代码就会陷入死循环,如果总返回false则代码逻辑根本无法执行到循环体内。
正常的测试逻辑应该是先返回几次true执行循环体,然后在返回false退出循环,使用Mock可以方便模拟这种预期的行为,代码如下:

EasyMock.expect(rs.next()).andReturn(true).times(2).andReturn(false).times(1);
更多的关于EasyMock的用法,请参考EasyMock官方文档:

http://easymock.org/EasyMock3_0_Documentation.html。

PowerMock:

上面介绍的EasyMock可以满足单元测试中的大部分需求,但是由于动态代理是使用了面向对象的继承和多态特性,JDK自身的动态代理只针对接口进行代理,其本质是为接口生成一个实现类,而CGLIB可以针对类进行代理,其本质是将类自身作为基类。

如果遇到了静态、final类型的类和方法,以及私有方法,EasyMock的动态代理局限性使得无法测试这些特性情况。

PowerMock是在EasyMock基础上进行扩展(只是补充,不是替代),使用了字节码操作技术直接对生成的字节码类文件进行修改,从而可以方便对静态,final类型的类和方法进行Mock,还可以对私有方法进行Mock,更可以对类进行部分Mock。

PowerMock的工作过程和EasyMock类似,不同之处在于需要在类层次声明@RunWith(PowerMockRunner.class)注解,以确保使用PowerMock框架引擎执行单元测试。

通过如下方式在maven添加PowerMock相关依赖:

<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-easymock</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>

例子如下:
(1).Miock final类的静态方法:

如果测试代码中使用到了java.lang.System类,代码如下:

public class SystemPropertyMockDemo {     
    public String getSystemProperty() throws IOException {     
        return System.getProperty("property");     
    }     
}
如果对System.getProperty()方法进行Mock,代码如下:
@RunWith(PowerMockRunner.class)     
@PrepareForTest({SystemPropertyMockDemo.class})//声明要Mock的类     
public class SystemPropertyMockDemoTest {     
    @Test    
    public void demoOfFinalSystemClassMocking() throws Exception {     
        PowerMock.mockStatic(System.class);//Mock静态方法     
        EasyMock.expect(System.getProperty("property")).andReturn("my property");//录制Mock对象的静态方法     
        PowerMock.replayAll();//重放Mock对象     
        Assert.assertEquals("my property",     
                                  new SystemPropertyMockDemo().getSystemProperty());     
        PowerMock.verifyAll();//验证Mock对象     
    }     

非final类的静态方法代码相同,注意(上述代码只能在EasyMock3.0之后版本正常运行)
如果要在EasyMock3.0之前版本正常Mock final类的静态方法,需要使用PowerMockito,

通过如下方式在maven中添加PowerMockito相关依赖:

<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>

代码如下:
@RunWith(PowerMockRunner.class)     
@PrepareForTest({SystemPropertyMockDemo.class})     
public class SystemPropertyMockDemoTest {     
    @Test    
    public void demoOfFinalSystemClassMocking() throws Exception {     
        PowerMockito.mockStatic(System.class);     
        PowerMockito.when(System.getProperty("property")).thenReturn("my property");     
        PowerMock.replayAll();     
        Assert.assertEquals("my property",     
                                  new SystemPropertyMockDemo().getSystemProperty());     
        PowerMock.verifyAll();     
    }     
}
注意:

对于JDK的类如果要进行静态或final方法Mock时,@PrepareForTest()注解中只能放被测试的类,而非JDK的类,如上面例子中的SystemPropertyMockDemo.class。

对于非JDK的类如果需要进行静态活final方法Mock时, @PrepareForTest()注解中直接放方法所在的类,若上面例子中的System不是JDK的类,则可以直接放System.class。

@PrepareForTest({......}) 注解既可以加在类层次上(对整个测试文件有效),也可以加在测试方法上(只对测试方法有效)。

(2).Mock非静态的final方法:

被测试代码如下:

public class ClassDependency {
public final boolean isAlive() {
return false;
}
}

public class ClassUnderTest{
public boolean callFinalMethod(ClassDependency refer) {
return refer.isAlive();
}
}
使用PowerMock的测试代码如下:
@RunWith(PowerMockRunner.class)        
public class FinalMethodMockDemoTest {     
    @Test  
    @PrepareForTest(ClassDependency.class)  
    public void testCallFinalMethod() {  
        ClassDependency depencency = PowerMock.createMock(ClassDependency.class); //创建Mock对象
        ClassUnderTest underTest = new ClassUnderTest();  
        EasyMock.expect(depencency.isAlive()).andReturn(true);  
PowerMock.replayAll();
        Assert.assertTrue(underTest.callFinalMethod(depencency));  
PowerMock.verifyAll();
    }
}
(3)部分Mock和私有方法Mock:
如果被测试类某个方法不太容易调用,可以考虑只对该方法进行Mock,而其他方法全部使用被测试对象的真实方法,可以考虑使用PowerMock的部分Mock,被测试代码如下:

public class DataService {
public boolean replaceData(final String dataId, final byte[] binaryData) {
return modifyData(dataId, binaryData);
}
public boolean deleteData(final String dataId) {
return modifyData(dataId, null);
}

private boolean modifyData(final String dataId, final byte[] binaryData) {
return true;
}
}
只对modifyData方法进行Mock,而其他方法调用真实方法,测试代码如下:
@RunWith(PowerMockRunner.class)
@PrepareForTest(DataService.class)
public class DataServiceTest {
@Test
public void testReplaceData() throws Exception {
DataService tested = PowerMock.createPartialMock(DataService.class, “modifyData”);//创建部分mock对象,只对modifyData方法Mock
PowerMock.expectPrivate(tested, “modifyData”, “id”, null).andReturn(true);//录制私有方法
PowerMock.replay(tested);
assertTrue(tested.deleteData(“id”));
PowerMock.verify(tested);
}
}
部分Mock在被测试方法的依赖在同一个类,且不容易创建时比较有用。
个人认为私有方法的Mock意义不是很大,完全可以使用反射机制直接调用。

(4).调用对象的构造方法Mock对象:

在被测试方法内部调用构造创建了一个对象很常见,被测试代码如下:

public class PersistenceManager {
public boolean createDirectoryStructure(String directoryPath) {
File directory = new File(directoryPath);
if (directory.exists()) {
throw new IllegalArgumentException("\"" + directoryPath + "\" already exists.");
}
return directory.mkdirs();
}
}
创建文件操作(new File(path))依赖与操作系统底层实现,如果给定的路径不合法,将会出现异常导致测试无法正常覆盖,此时需要使用PowerMock的提供的调用构造方法创建Mock对象,测试代码如下:
@RunWith(PowerMockRunner.class)
@PrepareForTest( PersistenceManager.class )
public class PersistenceManagerTest {
@Test
public void testCreateDirectoryStructure_ok() throws Exception {
File fileMock = PowerMock.createMock(File.class);
PersistenceManager tested = new PersistenceManager();
PowerMock.expectNew(File.class, "directoryPath").andReturn(fileMock);
EasyMock.expect(fileMock.exists()).andReturn(false);
EasyMock.expect(fileMock.mkdirs()).andReturn(true);
PowerMock.replay(fileMock, File.class);
assertTrue(tested.createDirectoryStructure("directoryPath"));
PowerMock.verify(fileMock, File.class);
}
}
也可以使用更简便的方法:
FilefileMock = PowerMock.createMockAndExpectNew(File.class,“directoryPath”);

通过EasyMock+PowerMock,开发中绝大部分的方法都可以被测试完全覆盖。

更多关于PowerMock的用法和参考文档请参考PowerMock官方网址:

https://code.google.com/p/powermock/。

程序员必看!转型人工智能最快速、最轻松解决办法
---------------------
作者:chjttony
来源:CSDN
原文:https://blog.csdn.net/chjttony/article/details/14522771?utm_source=copy
版权声明:本文为博主原创文章,转载请附上博文链接!

有效使用Mock编写java单元测试的更多相关文章

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

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

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

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

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

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

  4. Java单元测试框架 JUnit

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

  5. 轻松编写 C++ 单元测试

    单元测试概述 测试并不只是测试工程师的责任,对于开发工程师,为了保证发布给测试环节的代码具有足够好的质量( Quality ),为所编写的功能代码编写适量的单元测试是十分必要的. 单元测试( Unit ...

  6. 换种思路写Mock,让单元测试更简单

    开篇引入 单元测试中的Mock方法,通常是为了绕开那些依赖外部资源或无关功能的方法调用,使得测试重点能够集中在需要验证和保障的代码逻辑上.在定义Mock方法时,开发者真正关心的只有一件事:" ...

  7. Java单元测试技术1

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

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

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

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

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

随机推荐

  1. scrollspy.js--bug

    /** * 20140505 14.33 ycx * scrollspy.js中存在的bug!!!---为什么ui.tabs必须在scrollspy.js中的window.onload之前执行,也就是 ...

  2. 理解Promise

    一.Propmise基本用法 Promise用于发送一个异步完成的结果,是替代回调函数的另一种选择.可以把Promise理解为一种异步函数. 以下函数通过一个Promise来异步地返回一个结果 fun ...

  3. MySQL Explain详解(转)

    explain SELECT a.* FROM test a,(select id from test where level_id <=4 order by aa_id limit 24300 ...

  4. HDOJ1171(多重背包)

    #include<iostream> #include<cstdio> using namespace std; #define MAX(a,b) (a>b)?a:b + ...

  5. 对API的理解

    一. API(Application Programming Interface,应用程序编程接口) 1)定义:API是远程服务器或者操作系统的一些函数,是它们的一部分: 2)功能:用来接收应用程序( ...

  6. 杂项-协议-HTTP:GET/POST/PUT/DELETE/INPUT/TRACE/OPTIONS/HEAD方法

    ylbtech-杂项-协议-HTTP:GET/POST/PUT/DELETE/INPUT/TRACE/OPTIONS/HEAD方法 1.返回顶部 1. 请求方法是请求一定的Web页面的程序或用于特定的 ...

  7. varnish安装和配置

    实验环境:CentOS7 Varnish是高性能开源的反向代理服务器和HTTP缓存服务器. #varnish服务器:172.16.252.142 [root@varnish localhost]#yu ...

  8. 通信端口Com口被占用的原因分析

    目前在调试地磅读取程序,近一段时间无法读取,排查原因发现是com1端口被占用. 从网上找了无数个文章,最终得到一条有价值的消息, 原因如下: com1端口不能读取电子地磅的数据了,重启之后发现 有一个 ...

  9. jquery 键盘事件的使用方法详解

    转自:https://www.jb51.net/article/123579.htm jQuery处理键盘事件有三个函数,根据事件发生的顺序分别是: jquery 代码: 1.  keydown(); ...

  10. [hdu1712]ACboy needs your help分组背包

    题意:一共$m$天,$n$门课程,每门课程花费$i$天得到$j$的价值,求最后获得的最大价值 解题关键:分组背包练习,注意循环的顺序不能颠倒 伪代码: $for$ 所有的组$k$   $for{\rm ...