注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

0与1构筑世界,程序员创造时代

软件架构设计 Java编程

 
 
 

日志

 
 

用EasyMock生成模拟对象进行单元测试 | Testing with EasyMock  

2014-05-28 19:47:02|  分类: Java |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

当项目达到一定规模时会根据业务或功能进行模块化,这时研发团队在协作时会碰到一个问题:如何让相互依赖的各模块并行开发?常用的办法是:各模块之间有清晰的边界,模块之间的交互通过接口。在设计阶段定义模块的接口,然后各个模块开发时遵循接口的定义进行实现和调用。

OK,并行开发的问题解决了。但不同的模块因其复杂度和工作量不同,进度不一致。当研发同学完成所负责的模块时,但依赖的模块还没有完成开发,不好进行测试。 这里就讲如何用EasyMock生成Mock Object模拟所依赖模块的接口来完成单元测试。

用EasyMock生成模拟对象进行单元测试 | Testing with EasyMock - 傲风 - 0与1构筑世界,程序员创造时代

预备

  • easymock-3.2.jar
  • easymockclassextension-3.2.jar
  • cglib-2.2.2.jar
  • objenesis-1.2.jar
  • asm-3.1.jar
  • asm-commons-3.1.jar
  • asm-util-3.1.jar
  • gson-2.2.4.jar // 用于处理JSON

注:上面的jar文件均可从MAVEN仓库下载。

使用EasyMock的五部曲

1、引入EasyMock。

import static org.easymock.EasyMock.*;

2、创建Mock Object。

mock = createMock(InterfaceOrClass.class);

3、设置Mock Object的行为和预期结果。

mock.doSomeThing();
expectLastCall().times(1);   // 至少要调用一次doSomeThing方法

或者

// 模拟抛出异常
expect(mock.getSomeThing(anyString()))
    .andThrow(new IOException("单元测试特意抛的异常")); 

// 模拟返回指定的数据
expect(mock.getSomeThing(anyString()))
    .andReturn("abcd");  

4、设置Mock Object变成可用状态。

replay(mock);

5、运行单元测试代码(会调用到Mock Object的方法)。

// 如果是校验调用次数就要用到verify方法
verify(mock);

实践

业务代码

源码下载

package cn.aofeng.demo.easymock;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;

import org.apache.log4j.Logger;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import cn.aofeng.demo.jetty.HttpGet;

/**
 * 用户相关服务。如:获取用户昵称。
 * 
 * @author <a href="mailto:aofengblog@163.com">聂勇</a>
 */
public class UserService {

    private static Logger _logger = Logger.getLogger(UserService.class);

    private HttpGet _httpGet = new HttpGet();

    /**
     * 根据用户的账号ID获取昵称。
     * 
     * @param accountId 用户的账号ID
     * @return 如果账号ID有效且请求成功,返回昵称;否则返回默认的昵称"用户xxx"。
     */
    public String getNickname(String accountId) {
        String targetUrl = "http://192.168.56.102:8080/user?method=getNickname&accountId="+accountId;
        String response = null;
        try {
            response = _httpGet.getSomeThing(targetUrl);
        } catch (IOException e) {
            _logger.error("获取用户昵称时出错,账号ID:"+accountId, e);
        }

        if (null != response) {
            // 响应数据结构示例:{"nickname":"张三"}
            Type type = new TypeToken<Map<String, String>>() {}.getType();
            Map<String, String> data = new Gson().fromJson(response, type);
            if (null != data && data.containsKey("nickname")) {
                return data.get("nickname");
            }
        }

        return "用户"+accountId;
    }

    protected void setHttpGet(HttpGet httpGet) {
        this._httpGet = httpGet;
    }

}

源码下载

package cn.aofeng.demo.jetty;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import org.apache.commons.io.IOUtils;

/**
 * HTTP GET请求。
 * 
 * @author <a href="mailto:aofengblog@163.com">聂勇</a>
 */
public class HttpGet {

    public String getSomeThing(String urlStr) throws IOException {
        URL url = new URL(urlStr);
        HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
        urlConn.setConnectTimeout(3000);
        urlConn.setRequestMethod("GET");
        urlConn.connect();

        InputStream ins = null;
        try {
            if (200 == urlConn.getResponseCode()) {
                ins = urlConn.getInputStream();
                ByteArrayOutputStream outs = new ByteArrayOutputStream(1024);
                IOUtils.copy(ins, outs);
                return outs.toString("UTF-8");
            }
        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(ins);
        }

        return null;
    }

}

单元测试代码

源码下载

package cn.aofeng.demo.easymock;

import static org.junit.Assert.*;

import java.io.IOException;

import static org.easymock.EasyMock.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import cn.aofeng.demo.jetty.HttpGet;

/**
 * {@link UserService}的单元测试用例。
 * 
 * @author <a href="mailto:aofengblog@163.com">聂勇</a>
 */
public class UserServiceTest {

    private HttpGet _mock = createMock(HttpGet.class);

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
        reset(_mock);
    }


    /**
     * 测试用例:获取用户昵称 <br/>
     * 前置条件:
     * <pre>
     * 网络请求时发生IO异常
     * </pre>
     * 
     * 测试结果:
     * <pre>
     * 返回默认的用户昵称"用户xxx"
     * </pre>
     */
    @Test
    public void testGetNickname4OccursIOError() throws IOException {
        // 设置Mock
        expect(_mock.getSomeThing(anyString()))
                .andThrow(new IOException("单元测试特意抛的异常"));
        replay(_mock);

        UserService us = new UserService();
        us.setHttpGet(_mock);
        String nickname = us.getNickname("123456");
        verify(_mock); // 校验mock
        assertEquals("用户123456", nickname); // 检查返回值
    }

    /**
     * 测试用例:获取用户昵称 <br/>
     * 前置条件:
     * <pre>
     * 1、网络请求成功。
     * 2、响应状态码为200且响应内容符合接口定义({\"nickname\":\"张三\"})。
     * </pre>
     * 
     * 测试结果:
     * <pre>
     * 返回"张三"
     * </pre>
     */
    @Test
    public void testGetNickname4Success() throws IOException {
        // 设置Mock
        _mock.getSomeThing(anyString());
        expectLastCall().andReturn("{\"nickname\":\"张三\"}");
        expectLastCall().times(1);
        replay(_mock);

        UserService us = new UserService();
        us.setHttpGet(_mock);
        String nickname = us.getNickname("123456");
        verify(_mock); // 校验方法的调用次数
        assertEquals("张三", nickname); // 校验返回值
    }

}


<正文结束>


文章声明


作者:傲风(aofengblog@163.com)       编写时间:2014年05月28日

网址:http://aofengblog.blog.163.com

作者保留所有权利,转载请保留文章全部内容或者说明原作者和转载地址!

  评论这张
 
阅读(574)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017