项目里会集成外部的web services,怎么测试?当然最不费脑子的做法就是假装它没什么特别,该怎么写测试还怎么写测试。但这么做的结果就是一大堆测试都会真的去调用webservices,并且至少造成这么几个问题:
- 测试慢。带着web services跑测试,测试的速度会降低好几个数量级。
- 测试不稳定。一旦有个service挂掉,你自己的build就会跟着挂掉。
- 测试不易重复。比如有个service是用来创建用户的,你怎么办?每次跑build创建一个新用户?还是创建完马上删掉?如果删除出错怎么办?
所以这是不行的。
我们先站在集成点外来看其他程序怎么使用集成点。显然最终我们会把一个web service包装成一个Java方法。比如“创建用户”这个service最终会体现成这样:
public interface IdentityService {
void createUser(Customer customer);
…
那么使用这个service的所有代码都应该通过依赖注入得到实现IdentityService接口的一个对象,因此这些地方的测试可以很简单地注入一个mock的IdentityService对象,不需要依赖真正的webservice。主要需要看的,还是集成点内部的测试。
集成点内(也就是IdentityService.createUser的实现)实际上有以下几部分逻辑:
- 确定服务地址。不同的服务方法通常位于不同的URL、接受不同的HTTP方法(GET、POST或PUT)。
- 参数转换。把方法参数转换成service需要的参数,可能是(但不限于)XML文档或URL参数。
- 执行网络访问。朝向步骤(1)确定的服务地址,发送步骤(2)得到的参数,拿回一个HTTP应答。
- 解析HTTP应答。HTTP应答通常包含两部分:HTTP状态码(例如“200SUCCESS”或者“404NOT FOUND”),以及应答正文(不一定有),有时还会在HTTP头或者Cookie中携带信息。要把这些信息转换成服务方法调用的结果(函数返回值、给参数传入的对象填充值、或者抛出异常)。
可以看到,真正与外部web services打交道的其实只有步骤(3)而已。并且步骤(3)所需的程序基本上可以简化地描述为:
public class EndPoint {
public Response get(String url);
public Response post(String url, String requestBody);
public Response put(String url, String requestBody);
}
如此而已。其中Response类包含以下信息:
class Response {
private int statusCode;
private String body;
请注意:从EndPoint的方法签名可以看到,这里的方法与真实的web services的请求/应答格式是毫无关系的。它所做的就是朝一个URL发送一个请求、拿回应答。至于请求正文是什么格式、应答正文是什么格式、是XML还是JSON,它完全不关心。换句话说,对唯一的这个产生网络访问的类的测试,第一不需要访问真正的web services,第二不需要考虑真正web services的请求/应答协议格式。这个发现对于我们的测试策略非常有价值。
(今天讲了集成点的结构,并着重考察了需要执行网络访问的EndPoint类。下一篇就开始讲测试策略。)