您的位置:首页 > 其它

JUnit学习笔记6---用stub进行粗粒度测试

2012-06-23 15:54 375 查看
原帖  http://blog.csdn.net/watson243671/article/details/5282688

本章内容:
介绍stub
使用嵌入式服务器代替真正的网络服务器
使用stub单元测试一个http链接案例

stub简介

但开发应用程序时,发现现在需要测试的类还要依赖于其他的类库,总之是一个没有完善的开发环境,则在实际开发中时经常遇见的情况,例如,你的应用程序使用http连接由第三方提供web服务器,但是在你开发环境下通常不存在那样的一个可用服务器程序,所以需要模拟服务器。还有一种情况就是和其他的开发者一起开发时,你想测试自己的那部分,但是其他的部分还没有完成。在这些情况下,stub为我们提供了解决方案!

定义  stub——stub是代码的一部分。在运行时我们用stub替换真正代码,忽略调用代码的实现。目的是用简单一点的行为替换一个真正的行为,从而允许独立的测试代码的某个部分。

使用stub的例子:


 你不能修改一个现有的系统,因为它很复杂,很容易崩溃


粗粒度测试,如在不同的子系统之间进行集成测试

缺点:


 stub通常是很复杂,他们本身需要调试


因为的复杂性,他们可能会很难维护



stub不能很好的运用于细粒度的测试




使用不同情况要不同的策略


一个HTTP连接的例子


为了演示stub能做些什么,我们为一个简单的程序创建一些stub,他根据URL打开一个Http连接,同时读取其中的信息。

实际情况:看不到图的朋友点这里!





我们假设远程web资源是个servlet,它以某种方法(我们说,调用一个JSP)产生HTML响应。

使用了stub的情况:





待测试代码

 

 打开http示例的代码,也就是我们在后面进行测试的代码!


 

package junitbook.coarse.try1;

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

public class WebClient
{
public String getContent(URL url)
{
StringBuffer content = new StringBuffer();

try
{
HttpURLConnection connection =                             ;打开到URL的http连接
(HttpURLConnection) url.openConnection();              ;
connection.setDoInput(true);                               ;

InputStream is = connection.getInputStream();              ;开始读取远程数据

byte[] buffer = new byte[2048];
int count;
while (-1 != (count = is.read(buffer)))                    ;把所有的数据都读入流中
{                                                          ;
content.append(new String(buffer, 0, count));          ;
}                                                          ;
}
catch (IOException e)
{
return null;                                               ;出错时返回null
}

return content.toString();
}
}


选择一种替换方案

           在这个程序中可能有两种情况:远程web服务器位于开发平台的外围(如在合作站点上),或者,本身就是程序配置的平台的一部分。 不管怎样,为了能够对WebClient类进行单元测试,必须在开发平台上建立服务器!相对容易的方法就是为其安装一个Apache测试服务器,在它的文档根目录下放一些测试web页面。这是典型的、广泛使用的替换方法。但是缺点是非常明显的:

                       

 依赖环境——在测试前确保运行环境已经准备好了。如果web服务器关闭了,但测试被执行了,结果必然是错误的。


                      

分散的测试逻辑——测试逻辑被分散到两个不同的地方:一是在JUnit
test case,二是测试web页面。两种资源要同步。 

                      

测试难以实现自动化——自动测试还是很困难,因为它需要在web服务器上自动的配置web页面,自动启动web服务器,而
完成这一切仅仅是为了运行单元测试!


           幸运的,有一个更好的解决方案——使用嵌入式服务器Jetty!关于Jetty的一般信息,访问http://jetty.mortbay.org/jetty/index.html

            Jetty是轻量级的运行速度快的很好的web/servlet容器,使用Jetty可以消除前文的不足之处,服务器从JUnit test case开始运行,所有测试都在同一个位置用java编写,把test suite 自动化也成了一个微不足道的问题。得益于Jetty的模块性,做的事情只是用stub替换Jetty处理器,而不是替换整个服务器!


代码,以嵌入模式启动Jetty——JettySample类

package junitbook.coarse;

import org.mortbay.http.HttpContext;
import org.mortbay.http.HttpServer;
import org.mortbay.http.SocketListener;
import org.mortbay.http.handler.ResourceHandler;

public class JettySample
{
public static void main(String[] args) throws Exception
{
HttpServer server = new HttpServer();          ;创建Jetty HttpServer对象
SocketListener listener = new SocketListener();;在端口8080上给HttpServer对象绑上一个listener,这样它可以接收http请求

listener.setPort(8080);
server.addListener(listener);

HttpContext context = new HttpContext();        ;建立一个HttpContext,处理HTTP请求,把这些请求分发到不同的处理器那里

context.setContextPath("/");                    ;用setContextPath把context映像到根目录(/)URL上。
context.setResourceBase("./");                  ;setResourceBase设置文档根目录以提供资源。
context.addHandler(new ResourceHandler());      ;添加资源处理器到HttpContext,使之能提供文件系统中的文件。
server.addContext(context);

server.start();                                 ;启动服务器。
}
}


 替换Web服务器资源


建立第一个stub测试

      为了证明WebClient工作于有效的URL,你需要在测试启动Jetty服务器,这些都能在JUnit test case 里的setUp方法中实现!也可以用tearDown方法停止服务器的运行!

package junitbook.coarse.try1;

import java.io.IOException;
import java.io.OutputStream;

import junit.extensions.TestSetup;
import junit.framework.Test;

import org.mortbay.http.HttpContext;
import org.mortbay.http.HttpFields;
import org.mortbay.http.HttpRequest;
import org.mortbay.http.HttpResponse;
import org.mortbay.http.HttpServer;
import org.mortbay.http.SocketListener;
import org.mortbay.http.handler.AbstractHttpHandler;
import org.mortbay.util.ByteArrayISO8859Writer;

public class TestWebClientSetup1 extends TestSetup
{
protected static HttpServer server;

public TestWebClientSetup1(Test suite)
{
super(suite);
}

protected void setUp() throws Exception
{
server = new HttpServer();

SocketListener listener = new SocketListener();

listener.setPort(8080);
server.addListener(listener);

HttpContext context1 = new HttpContext();

context1.setContextPath("/testGetContentOk");
context1.addHandler(new TestGetContentOkHandler());
server.addContext(context1);

server.start();
}

protected void tearDown() throws Exception
{
server.stop();
}

private class TestGetContentOkHandler                        ;通过AbstractHttpHandler 类可以很容易的建立处理器
extends AbstractHttpHandler                              ;该类定义了一个简单的handle方法,你需要把它实现
{                                                            ;当收到的请求转到处理器上时,Jetty会调用这个方法
public void handle(String pathInContext, String pathParams,
HttpRequest request, HttpResponse response)
throws IOException
{
OutputStream out = response.getOutputStream();       ;使用由Jetty提供的ByteArrayISO8859Writer类,这样在
ByteArrayISO8859Writer writer =                      ;Http响应中返回你的字符就轻而易举了!
new ByteArrayISO8859Writer();

writer.write("It works");
writer.flush();
response.setIntField(
HttpFields.__ContentLength, writer.size());      ;设置相应内容的长度为写入输出流(这在Jetty中是必需的!)的字符长度
writer.writeTo(out);
out.flush();
request.setHandled(true);                            ;告诉Jetty,请求已经被处理了,不需要传给任何更深层的处理了
}
}
}


看了上面代码中的setUp和tearDown方法,大家会很快的注意到,我们需要准备一个包含有文本“It works”的静态页面。把该文本放在你的文档根目录中。但是,这显然是非常麻烦的,另一种方式就是,配置Jetty以使用你自己的处理器,它不用在系统文件中取字符,而是直接返回一个字符串。这是一个更强大的技术,即使远程服务器在你的WebClient客户上抛出一个异常,你还是能使用!

经过上面的一番配置,你已经为测试做好了准备,你需要解决的最后一件事情就是优化setUp/tearDown方法。上面的做法并不好,因为它要为每个测试启动和停止。虽然,Jetty很快,但是这种处理仍然没有必要,更好的是执行所有的测试,但是服务器只启动一次!幸运的是JUnit支持TestSetup概念,可以做到这点!TestSetup把一堆测试封装成一个suite,这个suite有setUp和tearDown方法,作用域是suite内的所有测试。


每个testsuite启动和停止Jetty各一次

package junitbook.coarse.try1;

import java.io.IOException;
import java.io.OutputStream;

import junit.extensions.TestSetup;
import junit.framework.Test;

import org.mortbay.http.HttpContext;
import org.mortbay.http.HttpFields;
import org.mortbay.http.HttpRequest;
import org.mortbay.http.HttpResponse;
import org.mortbay.http.HttpServer;
import org.mortbay.http.SocketListener;
import org.mortbay.http.handler.AbstractHttpHandler;
import org.mortbay.http.handler.NotFoundHandler;
import org.mortbay.util.ByteArrayISO8859Writer;

public class TestWebClientSetup3 extends TestSetup                   ;继承TestSetup 类,就可以使用全局的testSetup和tearDown
{
protected static HttpServer server;                              ;创建一个Jetty服务器,在后面调用!

public TestWebClientSetup3(Test suite)
{
super(suite);
}

protected void setUp() throws Exception
{
server = new HttpServer();

SocketListener listener = new SocketListener();

listener.setPort(8080);
server.addListener(listener);

HttpContext context1 = new HttpContext();

context1.setContextPath("/testGetContentOk");                    ;从下面开始是三个处理器!
context1.addHandler(new TestGetContentOkHandler());
server.addContext(context1);

HttpContext context2 = new HttpContext();

context2.setContextPath("/testGetContentNotFound");
context2.addHandler(new NotFoundHandler());
server.addContext(context2);

HttpContext context3 = new HttpContext();

context3.setContextPath("/testGetContentServerError");
context3.addHandler(new TestGetContentServerErrorHandler());
server.addContext(context3);

server.start();
}

protected void tearDown() throws Exception
{
server.stop();
}

private class TestGetContentOkHandler
extends AbstractHttpHandler
{
public void handle(String pathInContext, String pathParams,
HttpRequest request, HttpResponse response)
throws IOException
{
OutputStream out = response.getOutputStream();
ByteArrayISO8859Writer writer =
new ByteArrayISO8859Writer();

writer.write("It works");
writer.flush();
response.setIntField(
HttpFields.__ContentLength, writer.size());
writer.writeTo(out);
out.flush();
request.setHandled(true);
}
}

private class TestGetContentServerErrorHandler
extends AbstractHttpHandler
{
public void handle(String pathInContext, String pathParams,
HttpRequest request, HttpResponse response)
throws IOException
{
response.sendError(
HttpResponse.__503_Service_Unavailable);
}
}
}



测试类: 

package junitbook.coarse.try1;

import java.net.URL;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class TestWebClient3 extends TestCase
{
public static Test suite()
{
TestSuite suite = new TestSuite();
suite.addTestSuite(TestWebClient3.class);
return new TestWebClientSetup3(suite);
}

public void testGetContentOk() throws Exception
{
WebClient client = new WebClient();

String result = client.getContent(new URL(
"http://localhost:8080/testGetContentOk"));

assertEquals("It works", result);
}

public void testGetContentNotFound() throws Exception
{
WebClient client = new WebClient();

String result = client.getContent(new URL(
"http://localhost:8080/testGetContentNotFound"));

assertNull(result);
}

public void testGetContentServerError() throws Exception
{
WebClient client = new WebClient();

String result = client.getContent(new URL(
"http://localhost:8080/testGetContentServerError"));

assertNull(result);
}
}


回顾第一个stub

到这里就完成了很多的工作!对方法进行了单元测试而且还进行了集成测试,此外还检验了代码的逻辑,还测试了代码之外的连接部分。不足之处就是太复杂!

更大的不足之处就是,在这个例子中我们需要一个web服务器,另外的一个stub例子就不一样了,它需要不同的配置,经验会有所帮助,但是不同的情况需要不同的方案。

更好的stub:替换链接

现在我们是替换了web服务器的资源。改为替换HTTP连接又会怎样呢?这样做会妨碍你有效的测试连接,但是,这又怎样呢?因为这一点不是我们真正的目标,我们真正感兴趣的是孤立的测试代码逻辑,在以后可以用功能测试或是集成测试来检验连接。但我们不想改代码就进行测试的时候,你就会发现自己很幸运,因为JDK的URL和HttpURLConnection类允许我们引入自定义的协议处理器,处理任何类型的通信协议。你可以使任何对HttpURLConnection类调用指向你自己的测试类,这些类会返回测试中需要的任何东西。


创建自定义的URL协处理器

为了实现自定义的URL协处理器,要调用以下JDK方法,并把它传递给自定义的URLStreamHandlerFactory对象:

java.net.URL.setURLStreamHandlerFactory(

java.net.URLStreamHandlerFactory);

无论何时,只要调用一个URL.openConnection方法,URLStreamHandlerFactory类就被调用,返回一个URLStreamHandler对象。下面的代码给出了具体的实现,其想

就是在JUnit的setUp方法中调用静态的setsetURLStreamHandlerFactory方法。(更好的实现要用到前面讲的TestSetup类,以使它在testsuite执行期间只运行一次)


package junitbook.coarse.try2;

import junit.framework.TestCase;

import java.net.URL;
import java.net.URLStreamHandlerFactory;
import java.net.URLStreamHandler;
import java.net.URLConnection;
import java.io.IOException;

public class TestWebClient extends TestCase
{
protected void setUp()
{
URL.setURLStreamHandlerFactory(                  ;告诉URL类用工厂的方法来处理
new StubStreamHandlerFactory());
}

private class StubStreamHandlerFactory               ;把所有连接路由到HTTP处理器
implements URLStreamHandlerFactory
{
public URLStreamHandler createURLStreamHandler(
String protocol)
{
return new StubHttpURLStreamHandler();
}
}

private class StubHttpURLStreamHandler                ;提供返回stub HttpURLConnection类的处理器
extends URLStreamHandler
{
protected URLConnection openConnection(URL url)
throws IOException
{
return new StubHttpURLConnection(url);
}
}

public void testGetContentOk() throws Exception       ;检查WebClient类的测试
{
WebClient client = new WebClient();

String result = client.getContent(
new URL("http://jakarta.apache.org"));

assertEquals("It works", result);
}

}



创建JDK的HttpURLConnection stub

最后一步是创建一个HttpURLConnection类的stub实现,这样你就能返回测试时想要的任何值。

package junitbook.coarse.try2;

import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.io.InputStream;
import java.io.IOException;
import java.io.ByteArrayInputStream;

public class StubHttpURLConnection extends HttpURLConnection   ;HttpURLConnection 没有接口,继承它,复写它的方法
{
private boolean isInput = true;

protected StubHttpURLConnection(URL url)
{
super(url);
}

public InputStream getInputStream() throws IOException   ;复写getInputStream方法,返回字符串
{
if (!isInput)
{
throw new ProtocolException(
"Cannot read from URLConnection"
+ " if doInput=false (call setDoInput(true))");
}

ByteArrayInputStream bais = new ByteArrayInputStream(
new String("It works").getBytes());               ;预期的字符串作为返回值

return bais;
}

public void disconnect()
{
}

public void connect() throws IOException
{
}

public boolean usingProxy()
{
return false;
}
}



至此就可以运行测试了!

总结:在这一章里,我们示范了如何使用stub策略对访问远程web服务器的代

码进行了单元测试。我们使用了Jetty代替了远程的web服务器。Jetty的可嵌

如特性使我们专注于替换Jetty的http处理器,而不是整个容器。我们也为JDK

的HttpURLConnection写了stub,从而演示了更轻量的stub。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息