Take It From the Top, Part Two

Previously, I'd posted about testing a webapp We'd started at the top level, looking at how we could write our acceptance tests. This is all very well, but what's sitting underneath that? I'm not quite sure where to start here because there are two things that I've seen that I really like. Let's do the one that saves the most lines of code, and then, in another post, the other one.

Our application appears to the user as a series of pages. It makes sense that the acceptance tests should therefore query Page Objects about what it expects to see. Each Page Object represents a single page within your system and might be composed of smaller elements, such as a NavigationMenu. Personally, I like to query the Page Objects using objects representing what I expect to see on the page, other people like to simply use strings. Either way is fine, but the trick is to avoid exposing the raw structure of the page to the asking object, therefore:

accountPage.isAccountSummaryDisplayedFor("Barry");

is infinitely preferable to:

Iterator i = accountPage.getAccountSummaryTable().rows().iterator();
...

The Page Object relies on an interface wrapping our browser implementation (WebDriver and Browser are both names that I've seen) that exposes the current DOM which can then be queried using XPath. Because you're simply exposing the DOM and providing a few simple methods to submit forms and click links, the WebDriver interface can be pretty slim, reducing the pain of writing new implementations. In order for this to work, your webapp should be generating valid XHTML. At the very least, it should be returning well-formed XML.

There are several reasons to use XPath, but the most important ones are that it helps reduce the lines of code required to write tests, improving the maintainability of the tests, it's easy to read (especially if you stick with a limited subset of XPath), and it means that there aren't dozens of barely used classes cluttering your system ("Table", "TableRow", "TableCell", *shudder*) In addition, because these cluttering classes don't exist, you're far less tempted to break the encapsulation of the Page Objects. Hurrah!

Let's now think about the roles and responsibilities of the various layers we've just introduced. The acceptance tests are the only places where you should be making assertions (which also makes it clearer exactly where a test is failing because you don't need to dig through a stack trace). This means that the deeper levels should simply provide boolean methods for testing the (lack of) presence of constructs within the page. The acceptance tests have no idea of the structure of the page. This knowledge is held within the Page Objects, which know how to clickOnLinkForAccount("foo") and tell you isRecordDisplayedFor(barry). The Page Objects use XPath to query the DOM, which is obtained from the WebDriver. The WebDriver knows how to communicate with the outside world, how to click on links and submit forms.

So, looking back, the stack looks something like:

Acceptance Test makes assertions based on boolean methods on Page Objects, which use XPath to query the DOM returned by the WebDriver.

BTW, If you're interested, Mike Roberts has a post about using wikis to store acceptance tests.


Simon Stewart on Sunday, 01 January, 2006

Posted in: /tech /tech/java

You may comment...


Categories