工厂方法重构策略

来源:互联网 发布:rss源码 编辑:程序博客网 时间:2024/06/06 03:56

先看下面的代码(模拟Web客户端读取远程URL上的资源):

public class WebClient
{
    public String getContent( URL url )
    {
        StringBuffer content = new StringBuffer(); //用于存储从远程URL读取的资源
        try
        {
            HttpURLConnection connection = (HttpURLConnection) url.openConnection(); //根据URL打开相应的连接
            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;
        }
        return content.toString();
    }
}

如果我们想测试getContent()方法,我们首先想到可以mock一个URL,但是URL是一个final类,该方法行不通,然后我们想到可以mock一个HttpURLConnection,我们就应该让url的openConnection方法返回一个MockURLConnection,但是url是JDK的final类,我们无法控制它的内部方法,这是就应该想到重构WebClient类了。

下面是重构后的方法:

public class WebClient1
{

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

        try
        {
            HttpURLConnection connection = createHttpURLConnection( url );
            InputStream is = connection.getInputStream();


            int count;
            while ( -1 != ( count = is.read() ) )
            {
                content.append( new String( Character.toChars( count ) ) );
            }
        }
        catch ( IOException e )
        {
            return null;
        }


        return content.toString();
    }


    protected HttpURLConnection createHttpURLConnection( URL url )
        throws IOException
    {
        return (HttpURLConnection) url.openConnection();
    }
}

这相较以前的实现有什么好处呢?他们的处理逻辑没有改变,只是添加了一个方法。想想我们如何才能在HttpURLConnection connection = createHttpURLConnection( url )处使方法返回一个mock HttpURLConnection?只能是覆写该方法,因此可以写一个测试辅助类,继承现在的WebClient类,看下面的实现(该类应该放在测试类的内部):

private class TestableWebClient extends WebClient {

private HttpURLConnection connection;

public void setHttpURLConnection(HttpURLConnection connection) {

this.connection = connection;

}

public HttpURLConnection createConnection(URL url) {

return this.connection;

}

}

这时,我们就可以让HttpURLConnection connection = createHttpURLConnection( url )处的方法返回一个mock HttpURLConnection了。实现也很简单:

public class TestWebClient {

@Test

public void testGetContentOk() throws Exception {

MockHttpURLConnection mockConnection = new MockHttpURLConnection();

mockConnection.setExpectedInputStream(

new ByteArrayInputStream("It works".getBytes()));

TestableWebClient client = new TestableWebClient();

client.setHttpURLConnection(mockConnection);

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

assertEquals("It works", result);

}

}

上面的MockHttpURLConnection实现就不用说了,懂测试的人看上面的调用过程就可以写出来了。

综上可知:当我们要mock方法中的某些类的时候,而这种类型的变量的值又被设定并且与环境相关。如:HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 我们在测试时就得提供一个可靠的URL,但是我们并不想关心URL,如何做到呢,只需将等式右边的表达式改写成方法,然后用另一个辅助测试类来继承这个类并将这个方法覆写,以返回我们想要的对象。这样我们就不用关心URL了,做到了被测试的代码与环境相分离。可能有人会说我们这样测试的是测试辅助类,这不用担心,其实他们的业务逻辑是一样的。

所以说,测试对开发好的程序是很重要的!


0 0