单元测试:从存根到模拟对象的进阶之旅
立即解锁
发布时间: 2025-08-18 02:03:41 阅读量: 1 订阅数: 4 

### 单元测试:从存根到模拟对象的进阶之旅
#### 1. 粗粒度存根测试
在软件开发中,测试是保证代码质量的重要环节。我们可以通过单元测试对代码进行细致检查,同时也可以通过集成测试确保各个组件之间的协作正常。不过,这种全面的测试功能往往伴随着较高的复杂度。
有一种更轻量级的解决方案,它专注于单元测试,而不进行集成测试。其原理是,虽然集成测试非常必要,但可以在单独的测试套件中运行,或者作为功能测试的一部分。
接下来,我们将介绍另一种存根方法,它比之前的方法更简单,不需要对整个 Web 服务器进行存根处理,并且更接近下一章要介绍的模拟对象策略。
#### 2. 存根连接
之前我们对 Web 服务器的资源进行了存根处理,现在我们考虑对 HTTP 连接进行存根处理。这样做虽然无法有效测试连接,但这并不是当前的主要目标,我们更关注的是独立测试代码逻辑。功能测试或集成测试会在后续阶段对连接进行测试。
幸运的是,JDK 的 `URL` 和 `HttpURLConnection` 类允许我们插入自定义协议处理程序来处理各种通信协议。我们可以将对 `HttpURLConnection` 类的任何调用重定向到自己的测试类,该类将返回测试所需的任何结果。
##### 2.1 生成自定义 URL 协议处理程序
要实现自定义 URL 协议处理程序,需要调用 JDK 的以下方法,并传递一个自定义的 `URLStreamHandlerFactory` 对象:
```java
java.net.URL.setURLStreamHandlerFactory(
java.net.URLStreamHandlerFactory);
```
当调用 `URL.openConnection` 方法时,`URLStreamHandlerFactory` 类将被调用以返回一个 `URLStreamHandler` 对象。以下是实现此功能的代码示例:
```java
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(
new StubStreamHandlerFactory());
}
private class StubStreamHandlerFactory implements URLStreamHandlerFactory {
public URLStreamHandler createURLStreamHandler(String protocol) {
return new StubHttpURLStreamHandler();
}
}
private class StubHttpURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL url) throws IOException {
return new StubHttpURLConnection(url);
}
}
public void testGetContentOk() throws Exception {
WebClient client = new WebClient();
String result = client.getContent(new URL("http://localhost"));
assertEquals("It works", result);
}
}
```
在上述代码中,我们使用了几个内部类来传递 `StubHttpURLConnection` 类。也可以使用匿名内部类来简化代码,但这会使代码更难阅读。需要注意的是,我们还没有编写 `StubHttpURLConnection` 类,这将在接下来的部分介绍。
##### 2.2 创建 JDK HttpURLConnection 存根
最后一步是创建 `HttpURLConnection` 类的存根实现,以便为测试返回所需的任何值。以下是一个简单的实现,它将字符串 “It works” 作为流返回给调用者:
```java
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 {
private boolean isInput = true;
protected StubHttpURLConnection(URL url) {
super(url);
}
public InputStream getInputStream() throws IOException {
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;
}
}
```
由于 `HttpURLConnection` 没有提供接口,我们需要继承它并覆盖想要存根的方法。在这个存根中,我们只实现了 `getInputStream` 方法,因为这是被测试代码唯一使用的方法。如果被测试代码使用了 `HttpURLConnection` 的更多 API,我们还需要对这些额外的方法进行存根处理。
##### 2.3 运行测试
运行使用 `StubHttpURLConnection` 的 `TestWebClient` 测试,结果表明,存根连接比存根 Web 资源更容易。虽然这种方法的测试级别不如集成测试,但它使我们能够更轻松地为 `WebClient` 代码逻辑编写专注的单元测试。
#### 3. 模拟对象测试介绍
单元测试的理想目标是将每个方法与其他方法或环境隔离开来进行测试。之前我们了解了存根技术,它可以将代码与环境隔离开来进行单元测试。那么,是否可以实现更细粒度的隔离,例如隔离对另一个类的方法调用呢?答案是肯定的,这种技术就是模拟对象。
模拟对象由 Tim Mackinnon、Steve Freeman 和 Philip Craig 在 XP2000 上首次提出。模拟对象策略允许我们进行最精细的单元测
0
0
复制全文
相关推荐










