16 November 2009

Keeping the cost of functional test down

It's essential for every developers and QAs in a team to have shared understanding of the environment against which functional tests are run. Only then you can talk about testing strategies. Why? More often than not, running functional tests against production like environment proves to be quite an expensive exercise. To Measure if it's valuable exercise or not, and to come up with strategies that minimise the risk of not having production like environment requires input from both developers and QAs.

In hypothetical scenario, let one of such differences between functional test environment and production environment be non-existence of application server. One might argue that it's slow and tedious to deploy application into application server every time which does slow down the continuous build. Another might argue that abstracting away from the environment that application server provides and enforces upon is simply too labour intensive. While QAs simply not happy with risk exposed from possibly not testing the integration point between application and application server, and anything that falls off the boundary of application. This sounds like a good place to get everyone talking. It might turn out that QAs are convinced that one or two integration tests are going to be ok given that the nature of integration is indifferent across the few integration points. However the discussion turns out to end, important thing is that everyone should have shared understanding at the end.

Whether by accident or common desire, there have been some efforts from open source communities to abstract away from existing servlet interface with relatively new Java API for RESTful web services. Most of the available implementations have an adaptor that bridges between servlet (i.e. an existing application server environment) and their own abstraction. This makes it a reasonable platform to write out-of container functional tests driven by HtmlUnitDriver with modified WebClient to intercept outgoing request to application.

public class OutOfContainerHtmlUnitDriver extends HtmlUnitDriver {
  private final InternalRequestDispatcher dispatcher;
  private final ResponseHandler responseHandler;
  private final List stubbedExternalResources;

  @Inject
  public OutOfContainerHtmlUnitDriver(InternalRequestDispatcher dispatcher, List stubbedExternalResources) {
    this.dispatcher = dispatcher;
    this.stubbedExternalResources = stubbedExternalResources;
    this.responseHandler = new ResponseHandler();
  }
 
  @Override
  protected WebClient modifyWebClient(WebClient client) {
    new WebConnectionWrapper(client) {
      @Override
      public WebResponse getResponse(WebRequestSettings request) throws IOException {
        try {
          if (dispatcher.isInternalRequest(request.getUrl().toURI())) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            Response response = dispatcher.dispatch(new HtmlUnitRequestAdaptee(request));
            stopWatch.stop();
            return new WebResponseImpl(
              responseHandler.webResponseData(response),
              responseHandler.charset(response),
              request,
              stopWatch.getTime());
          } else {
            for (StubbedExternalResource stubbedExternalResource : stubbedExternalResources) {
              if(stubbedExternalResource.supports(request)) {
                return stubbedExternalResource.handle(request);
              }
            }
            throw new RuntimeException(String.format("External resource '%s' is not stubbed.", request.getUrl()));
          }
        } catch (URISyntaxException e) {
          throw new RuntimeException(e);
        }
      }
    };

    client.setPrintContentOnFailingStatusCode(true);
    client.setThrowExceptionOnFailingStatusCode(true);
    return client;
  }
}

What I've got above is an InternalRequestDispatcher that understands difference between internal request and external request. It also wraps around the application so that it can dispatch internal request to. ResponseHandler is responsible for conversion between HtmlUnit response and the application response.