Test-driven Test Code Development - Unit Testing Page Objects

Markus Gaertner, it-agile GmbH, www.shino.de/blog

Test automation is software development. Some people learn this the hard way. But if we take this sentence seriously, wouldn�t it mean that we have to use the same practices to develop our automation code that we use to write our production code? That would mean that we should use continuous integration, code metrics, as well as design patterns. But foremost doesn�t that mean that we should use test-driven development to develop it?

In the following article I will exploit this idea. We will discuss several ways to use test-driven development for the page object pattern. The page object pattern is used to test web applications. The popular webtesting toolkit Selenium [1] provides build-in support for page objects. But let�s first define what the page object pattern is, and then explore some of the options we have for test-driving it.

I will focus on Java implementations for page objects. The mechanisms are similar in different languages.

PageObjects 1on1

The main idea behind the page object pattern is to encapsulate dependencies towards the user interface in objects. Sometimes folks use a hierarchy of objects for this, but often a collection of plain objects does the trick as well. A page object is an object or class in programming code that provides fields and functions to manipulate the contents of a web page. One page object represents one particular page in your web application, or it could represent one particular piece of multiple web pages, like a re-occurring search field on multiple pages of your web application.

Consider a web application page that is composed by different page elements like text fields, text links and buttons. We interact with the application by either entering some text on the web page or clicking on a link or a button. These are two typical interactions. For entering information like text fields or selecting an entry from a dropdown list, the page object provides methods that take parameters accordingly. For example if we have a form that takes three parameters, interprets them as side lengths for a triangle, and does something with that, we might want to come up with a function like the following:

public void enterSideLengths(String side1, String side2, String side3) throws Exception { ... }

Here we have a method that interacts with the page, and enters the given three side lengths into some fields.

For navigation there is either a link that we want to follow or a button that we press. We also encapsulate this behavior in its own function. Unlike pure interaction functions with the same page, these functions return a new page object where the according action will lead to. For example imagine we want to click a search result in a list of search results. Our page object might provide a convenient function to do so:

public ResultPage clickOnResult(String linkText) throws Exception { ... }

Any further actions will be followed up on the resulting page object.

While working with other frameworks like Cucumber [2], FitNesse [3], or whatever you may be using, the page object pattern provides separation of concerns. The interaction with the user interface is encapsulated in the page objects. The responsibility for the flow of actions lies then in the step definitions, fixture classes, or whatever your framework provides similar to that. That means that these classes will also need to take care of the bookkeeping, like on which page the driver currently is. If we want to unit test page objects, our unit tests will play the role of this particular support code, like step definitions or fixture classes.

There is another observation worth noting. Page objects can be expressed on a fairly low level, and on more and more abstract levels. For example for the triangle page we might provide three different functions, one for each side length. Or we might combine the three side lengths together in a single function. The trade-off we make as designers of this code lies in coupling. The code with three different functions for each side length will be coupled more tightly to the actual implementation of the user interface. If we, for example, decide to allow a single test field with three comma-separated side lengths in the future, we will need to change our tightly coupled code, while our single function page object just needs to change internally.

On the other hand, more abstract page objects might need more logic to handle different inputs. Usually that means that the cyclomatic complexity [4] for our page object will raise. The cyclomatic complexity metric measures the amount of conditional logic in a given piece of code. For a cyclomatic complexity of 10, we need at least 11 tests to end up with a good test coverage. In summary that means that we will need more unit tests if we go for the more abstract interfaces in our page objects. By avoiding the high coupling to the actual user interface, we have to write more tests for more complicated code.

Unit testing PageObjects

In order to really test-drive a page object, we have to find a way to instantiate our page object. There are two convenient ways that Selenium supports through the PageFactory.initElements(WebDriver driver, Class<?> clazz) method. Either the page object class provides a constructor that takes a WebDriver as an argument, or it does have a default constructor. The PageFactory.initElements method will look for any WebElement fields that are annotated with the @FindBy or @FindBys annotation, and initialize these using the driver to look up the according reference from the page itself. Since recent versions of Selenium, the PageFactory also supports WebElement fields that reflect the id or classname in their field name.

After initialization all WebElements will be instantiated so that any following function call can make use of all the fields, like using sendKeys on an input element. For a unit test I would prefer to use mocks instead of starting a browser each time. The class WebDriver itself is an interface. This is the same for WebElement. Therefore it�s pretty easy to mock these classes. For a unit test for a page object, I am going to use Mockito [5] to mock these dependencies. First, I annotate a WebDriver field in my unit test, and initialize the mocks and the page object in a dedicated setup. In a test I can then model the concrete interactions with the web elements that are on the page by mocking them as well:

@Mock WebDriver driver; SomePage page; @Before public void setUp() { initMocks(this); page = initElements(driver, SomePage.class); } @Test public void test() { WebElement element = mock(WebElement.class); when(driver.findElement(argThat(matchesSomethingImportant()))).thenReturn(element); }

For any interaction with the webpage, I will have to verify the according interaction on the mock that I return through my mocked driver:

@Test public void test() { WebElement element = mock(WebElement.class); when(driver.findElement(argThat(matchesSomethingImportant ()))).thenReturn(element); page.enterSomething("1234"); verify(element).sendKeys("1234"); }

In this example the input "1234" shall be forwarded to the important element. Using different matchers we can also model more complex interactions. At that point though, I would like to come up with a more fluent API for my unit tests. For the time being that given example should suffice though. Engaged readers should take a closer look into Growing Object-oriented Software - Guided by Tests from Steve Freeman and Nat Pryce [6].

Test my page objects

We took a brief look on page objects and how to treat them in unit tests. There is one striking thought: how good is the test given above? There are some flaws with it. It is highly coupled to the implementation of the page object itself. Once this page object changes only slightly, it�s likely that we will have to re-write our test cases. But that might be intended. In the long run we have to keep in mind the total cost of test ownership. If the page object tests turn out to be too brittle and fail too often unintended, then we might be better off without them. At least we should think about this situation, and follow-up as a team.

If we end up with more complicated page objects, we are better off writing some unit tests. Future test maintainers will be glad to have a reference with our current thoughts that they can program against. Using mocking will help make these tests fast without the need to start a real browser for WebDriver in the background. In my experience far too few people come up with the thought that test automation is software development, and we should also test our test automation code if it gets too complex. [7] Discuss in your team a threshold base line for getting started with unit tests for your test automation code. Your team and yourself will be glad about a base line unit test suite in the future once you need to maintain your test code.

References

[1] Selenium, http://www.seleniumhq.org

[2] Cucumber, http://www.cukes.info

[3] FitNesse, http://www.fitnesse.org

[4] A Complexity Measure, Thomas J. McCabe, IEEE Transactions on Software Engineering, Vol. SE-2, No. 4, December 1976

[5] Mockito, http://www.mockito.org

[6] Growing Object-oriented software guided by tests, Steve Freeman, Nat Pryce, Addison-Wesley Longman, 2009

[7] ATDD by Example: A Practical Guide to Acceptance Test-driven Development, Markus Gärtner, Addison-Wesley Longman, 2012

Test-Driven Development and Agile Testing Articles

Acceptance Test Driven Development (ATDD) Explained

Improving Application Quality Using Test-Driven Development (TDD)

Scrum and Agile Software Testing Knowledge

Software Testing Magazine

Software Testing Television

Click here to view the complete list of archived articles

This article was originally published in the Fall 2012 issue of Methods & Tools