在单元测试中,实际上经常遇到这种情况:某个方法依赖于其他一些难以掌握的东西,诸如网络、数据库、甚至是servlet引擎,那么该如何进行单元测试。实际上解决这些问题,我们需要的就是找一个“替身”来模拟这些情况。幸运的是,有一种测试模式可以帮助我们:mock对象

Mock对象

Mock对象就是真实对象在调试器的替代品。之所以使用mock对象来模拟条件而不是使用真实对象有以下原因:
1,真实对象具有不可确定的行为,产生不可预测的结果
2,真实对象很难被创建
3,真实对象的某些行为很难被触发(如上一章提到的网络错误)
4,真实对象令程序的运行速度很慢
5,真实对象有用户界面
6,测试需要询问真实对象它是如何被调用的(如:测试可能需要验证某个回调函数是否被调用)
7,真实对象实际上并不存在(当需要和其他的开发小组打交道,这是个不可避免的问题)
8,被测试代码只会通过接口来引用对象,所以它完全可以不知道它引用的究竟是真实对象还是mock对象

在使用mock对象进行测试的时候,有三个关键的步骤:
1,使用一个借口来描述这个对象
2,为产品代码实现这个接口
3,以测试为目的,在mock对象中实现这个接口

测试Servlet

Servlet是被web服务器管理的一大块代码,对URLs的请求转移给servlet容器,而容器又回过头来调用servlet的代码。servlet然后构建一个response返回给发出请求的浏览器。在这里给出servlet的测试例子是因为mock在很多时候是用来模拟一个request或者response以及相应的实体类的,用servlet做例子也能比较容易地看到测试代码与被测代码之间的调用关系。

我们以一个把华氏温度转化为设置温度的servlet的部分代码为例。可以看到servlet内部主要的功能就是从request中拿到Fahrenheit的值,然后交给try内的结构体去转化为摄氏度,如果参数无效则抛出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// TemperatureServlet.java
import junit.framework.*;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;
public class TemperatureServlet {
/*
public void init() throws ServletException{
String str_f = " ";
}*/
public void doGet(HttpServletRequest req,HttpServletResponse res)
throws ServletException, IOException{
String str_f = req.getParameter("Fahrenheit");
res.setContentType("text/html");
PrintWriter out = res.getWriter();
try {
int temp_f = Integer.parseInt(str_f);
double temp_c = (temp_f - 32)*5.0/9.0;
out.println( "Fahrenheit: " + temp_f + ", centigrade: " + temp_c);
}catch(NumberFormatException e) {
out.println( "Invalid temperature: "+str_f);
}
}
}

相应的测试代码其实就是要构建出requestresponse对象。其中,相应的HttpServletRequest和HttpServletResponse都是接口,所以我们要做的就是写出两个类来实现这个接口,并且使用它们。不要担心,真正使用是时候我们并不用自己来实现这些类,有现成的mock对象框架可供我们选用。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// TemperatureServletTest.java
import junit.framework.*;
import com.mockobjects.servlet.*;
import com.mockobjects.*;
import com.mockobjects.Verifiable;
public class TemperatureServletTest extends TestCase {
public void testTrue() throws Exception {
TemperatureServlet s = new TemperatureServlet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setupAddParameter("Fahrenheit", "212");
response.setExpectedContentType("text/html");
s.doGet(request, response);
response.verify();
assertEquals("Fahrenheit: 212, centigrade: 100.0\n" , response.getOutputStreamContents());
}
public void testError() throws Exception {
TemperatureServlet s = new TemperatureServlet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setupAddParameter("Fahrenheit", "wrong");
response.setExpectedContentType("text/html");
s.doGet(request, response);
response.verify();
assertEquals("Invalid temperature: wrong\n",response.getOutputStreamContents());
}
}

注:代码的运行需要很多的jar文件,相应的文件以及源码可在我的Github中下载

事实上,关于mock还是有很多好用的单元测试框架的,在下一章单元测试整理(六)—— 使用EasyMock和JUnit进行单元测试将介绍easyMock,并演示一个使用EasyMock和JUnit进行单元测试的实例。