Using HTTPUnit ‘out of the box’

Recently, HTTPUnit project reached version 1.6. While this nifty API is mainly targeted at unit testing webapps, I have also succesfully used it for other purposes such as :

HTTPUnit as a benchmarking tool

There is a plethora of web benchmarking tools out there, both freeware and commercial. However, my customer requested some features for testing, that I’ve had troubles satisfying simultaneously with the existing tools:

- the tests must run on headless systems (console mode, non GUI) - load testing should simulate complex and realistic user interactions with the site

AFAIK, all the testing tools that allow recording and replaying of intricate web interaction scenarios are GUI-based. And then, command-line tools are also unfit for the job, take for instance Apache Jmeter which is basically a command-line tool with a Swing GUI slapped on it. While Jmeter is great when it comes to bombing a server (or a cluster, for that matter) with requests, it seriously lacks features when it comes to scripting complex interaction (you’d better know your URL’s and cookies by heart … well, almost).

Another problem I see with existing automated testing solutions is with their error detection mechanisms. While the vast majority of tools are scanning for standard HTTP error codes such as 404 or 500 in order to find out if the response is erroneous or not, errors in complex Java apps might come as plain web pages containing strack traces and environment information (a good example is the error page in Apache Tapestry).

So eventually I had to come up with an ad-hoc solution – basic idea was to leverage the existing HTTP unit tests for benchmarking purposes. I had to get out of the toolbox another rather under-rated open-source gem: JUnitPerf, in fact a couple of decorators for Junit tests. LoadTest is the decorator I’m interested in : it allows running a test in multithreaded mode, simulating any number of concurrent users. Thus, I am able to reproduce heavy loads of complex user interaction and precisely count the number of errors. The snippet of code is something like:

SimpleWebTestPerf swtp = new SimpleWebTestPerf(“testOfMyWebapp”);Test loadTest = new LoadTest(swtp, nbConcurrentThreads);TestResult tr = TestRunner.run(loadTest);int nbErr = tr.errorCount();

Now, we’ll call this code with increasing values of nbConcurrentThreads and see where the errors start to appear. Might as well write the results in a log file and even create a nice PNG via JFreeChart. Alas, things become a little trickier when we want to measure the bandwidth; in our case we’ll have to write something very lame in the TestCase; and it goes like that:

    private long bytes = 0;
    private synchronized void addBytes(long b){
    bytes += b;
    }
    /**
    * After a test was run, returns the volume of HTTP data.
    * @return
    */
    public long getBytes(){ return bytes;}

    public void testProdsPerformance() throws MalformedURLException,  IOException, SAXException {
    [...]
    WebConversation wc = new WebConversation();
    WebResponse resp = wc.getResponse(something);
    addBytes(resp.getContentLength());
    [...]
    }

Then, in the benchmarking code, we’ll do swtp.getBytes() in order to find out how many bytes passed between the server and the test client. It is still unclear for me if this value is correct if mod_gzip is activated on the server (we might actually measure the bandwidth of the ‘deflated’ pages !?).

In order to measure the elapsed time, we’ll do a similar (lame) trick with a private long time member and a private synchronized void addTime(long millis). Unfortunately, we do not [yet?] have a getElapsedTime() for the WebResponse, so we’ll have to use the good old System.currentTimeMillis() before and after extraction of each WebResponse. Of course, this is also measuring the parsing time of WebResponse, but this isn’t usually a problem when you are testing a large number of concurrent users, as the parsing time is much smaller when compared with the response time of a stressed server. But, you’ll need a strong system for the client-side tests.

Another tip I’ve found: use Random in order to test different slices of data on different test runs. This way, when you run, let’s say, the 20 threads test, you’ll kick different data compared to the previous test, on 10 threads. In this manner, the results will be less influenced by the tested application cache(s). It’s perfectly possible to launch LoadTest threads with delays between thread activation, which means that the Random seed could be different within each simulated client – if you’re looking for even more realistic behavior.

HTTPUnit as a Web integration API

Besides being a great testing tool, Httpunit is also a cool API for Web manipulation, you can use it to perform data integration with all sorts of websites. For instance, let’s log on the public demo instance of MantisBT bug tracking system, as user ‘developer’, and extract the descriptions of the first three bugs in the list.

    package webtests;
    import java.io.IOException;
    import java.net.MalformedURLException;
    import org.xml.sax.SAXException;
    import com.meterware.httpunit.WebConversation;
    import com.meterware.httpunit.WebForm;
    import com.meterware.httpunit.WebResponse;
    import com.meterware.httpunit.WebTable;
    /**
    * Simple demo class using httpunit to extract the description of
    * three most recent bugs from the MantisBT public demo,
    * logged as 'developer'.
    * @author Adrian Spinei aspinei@yahoo.com
    * @version $Id: $
    */
    public class MantisTest{

    public static void main(String[] args) throws MalformedURLException, IOException, SAXException   {
            WebConversation wc = new WebConversation();
            WebResponse resp = wc.getResponse("http://mantisbt.sourceforge.net/mantis/login_page.php");
            WebForm wForm = resp.getFormWithName("login_form");
            wForm.setParameter("username", "developer");
            wForm.setParameter("password", "developer");
            //submit login, conect to front page
            resp = wForm.submit();
            //'click' on the 'View Bugs' link
            resp = resp.getLinkWith("View Bugs").click();
            //retrieve the table containing the bug list
            //you'll have to believe me on this one, I've counted the tables !
            WebTable webTable = resp.getTables()[3];
            //first three rows are : navigation and header, then a blank formatting row
            //interesting data starts from the 4th column
            System.out.println(webTable.getCellAsText(3, webTable.getColumnCount() - 1));
            System.out.println(webTable.getCellAsText(4, webTable.getColumnCount() - 1));
            System.out.println(webTable.getCellAsText(5, webTable.getColumnCount() - 1));
            }}

The code speaks for itself: HTTPUnit is beautiful, intuitive and easy to use.

Other interesting HTTPUnit-related articles:

Comments

Tags