Three practices for creating readable test code

You’ve probably read before that writing code that is readable and easy to understand is considered good practice for any software developer. Readable code takes less time to understand, to hand over to your colleagues or clients and to update if needs be. Since writing automated checks is essentially just another form of software development (this was true in 2005 and it sure hasn’t changed since then), it makes sense to apply the ‘write readable code’ practice there as well. But how do you go about doing that? In this post, I would like to introduce three practices that are guaranteed to have a positive impact on the readability of your test code.

Practice: Use fluent assertions libraries
Some other fans of readable code with far better development skills than yours truly have created some useful libraries that allow you to write assertions in a style that is almost equal to natural languauge, so-called fluent assertions. Two examples of these fluent assertion libraries for Java are AssertJ and Truth.

Let’s take AssertJ as an example. Consider the following TestNG assertions:

assertEquals(4, calculator.add(2,2));

assertTrue(muppets.hasMember("Kermit"));

assertEquals(0, mammals.getAnimalsByType("fish").size());

When converted to AssertJ assertions, these look like:

assertThat(calculator.add(2,2)).isEqualTo(4);

assertThat(muppets.hasMember("Kermit")).isTrue();

assertThat(mammals.getAnimalsByType("fish")).isEmpty();

The exact same checks are being executed, but the AssertJ assertions are the better readable ones in my opinion, since you can read them from the left to the right without having to jump back in order to understand what the assertion does. AssertJ has many more features for improving the readability of your test code, so be sure to check it out. The library also comes with a script that automatically converts your TestNG assertions to AssertJ assertions for you.

Practice: Have Page Object methods return Page Objects
This practice applies mostly to Selenium WebDriver code, although I can imagine there are other uses for it as well. When you use the Page Object model in your code, you can easily improve the readability of your tests by assigning Page Object return types to all methods in your Page Object. Each method represents a specific user action that is to be performed on the page, and the type of Page Object that is returned depends on the page where the user will end up after that specific action is performed. For example, a simple LoginPage object could look like this:

public class LoginPage {
	
	private WebDriver driver;
	
	@FindBy(id="txtFieldUsername")
	private WebElement txtFieldUsername;
	
	@FindBy(id="txtFieldPassword")
	private WebElement txtFieldPassword;
	
	@FindBy(id="btnLogin")
	private WebElement btnLogin;
	
	public LoginPage(WebDriver driver) {
		
		this.driver = driver;
		
		if(!driver.getTitle().equals("Login Page")) {
			driver.get("http://example.com/login");
		}
		
		PageFactory.initElements(driver, this);
	}
	
	public LoginPage setUsername(String username) {
		
		txtFieldUsername.sendKeys(username);
		return this;
	}
	
	public LoginPage setPassword(String password) {
		
		txtFieldPassword.sendKeys(password);
		return this;
	}
	
	public HomePage clickLoginButton() {
		
		btnLogin.click();
		return new HomePage(driver);
	}
}

Note that this Page Object also uses the PageFactory pattern, which improves Page Object code readability as well. This pattern is beyond the scope of this blog post, though.

Assuming we also have a HomePage object with a method that returns a boolean indicating we are indeed on the home page, we can write a simple login test that looks like this:

@Test
public void loginTest() {
		
	new LoginPage(driver)
		.setUsername("myUser")
		.setPassword("myPassword")
		.clickLoginButton();
		
	Assert.assertTrue(new HomePage(driver).isAt(), "Home page has been loaded successfully after login action");
}

You can instantly see what this test does, due to the fact that all of our Page Object methods return a specific type of Page Object. This enables us to chain methods from either the same or different Page Objects (as long as their return types match up) and create this type of readable tests.

Practice: Employ a Given/When/Then structure for your tests wherever possible
Another way to improve the readability of your test code is to structure your tests following the Given/When/Then format that is often used in Behaviour Driven Development (BDD). This applies even when you’re not doing BDD, by the way. Just think of it like this:

  • Given: set up test data and other preconditions
  • When: perform a specific action
  • Then: check the results

Some test tools support this format explicitly, for example REST Assured:

@Test
public void testMD5() {
		
	given().
		parameters("text", "test").
	when().
		get("http://md5.jsontest.com").
	then().
		body("md5",equalTo("098f6bcd4621d373cade4e832627b4f6")).
	and().
		body("original", equalTo("test"));
}

For other tools, you may need to apply this implicitly:

@Test
public void testCalculatorAddition() {
		
	//Given
	Calculator calculator = new Calculator();
	
	//When
	calculator.add(2);
		
	//Then
	assertEquals(calculator.getResult(), 2);		
}

An additional benefit of writing your tests in this way is that it makes it much easier to discuss them with people without any substantial programming background. Most people will recognize what a test does if you break them down in this way, even when they lack programming knowledge.

I hope there’s at least something that you can take away from this post and apply it to your existing test suite in order to make it more readable. You’ll be doing yourself, your clients and your fellow team members a huge favour.

"