The 3 “A”s For Building a Great Test Automation Suite

Writing high quality test automation suite is not easy, especially for manual testers who are relatively new to automation. There is a learning curve. But the cost of not writing high quality test code, both in terms of maintenance and time wasted on unreliable or hard-to-update tests, is unsustainable for any but the smallest project. This article looks at one simple technique that can help testers write better organised tests that are easier to understand and to maintain.

Arrange, Act, Assert – The 3 “A”s, are a common way of organizing unit tests, and they apply equally well to automated acceptance tests. Indeed, many real-world tests could be made more readable and easier to maintain simply by respecting these principles.

The Arrange-Act-Assert pattern breaks a test into three parts:

  • Arrange: prepare the context.
  • Act: perform the action we want to demonstrate.
  • Assert: show that the outcome we expected actually occurred.

In Behavior Driven Development (BDD), acceptance criteria is often organised in the same way, using the Given-When-Then format. The difference is one of the levels of abstraction: The Arrange-Act-Assert pattern describes what the test code is doing, whereas Given-When-Then reason in terms of what user action the test is describing. For this reason, I generally prefer to use the terms Given-When-Then for acceptance tests.

What does good look like? 

The following test illustrates the Arrange-Act-Assert (or Given-When-Then) structure:

@Test
public void should_be_able_to_check_in() {
    // ARRANGE / GIVEN
    PetHotel hotel = new PetHotel();
    Pet fido = Pet.dog().named("Fido");

    // ACT / WHEN
    hotel.checkIn(fido);

    // ASSERT / THEN
    assertThat(hotel.getRegisteredPets(), 
               hasItem(fido));
}

This test makes it very clear which action we are illustrating (checking in a pet to the pet hotel), and what outcome we expect (the Fido should be in the list of registered pets).

On the other hand, the following test is less clear:

@Test
public void checking_in_several_pets() {
    PetHotel hotel = new PetHotel();
    Pet fido = Pet.dog().named("Fido");
    Pet ginger = Pet.cat().named("Ginger"); 
    hotel.checkIn(fido);
    hotel.checkIn(ginger); 
    assertThat(hotel.getRegisteredPets(), 
               hasItems(fido,ginger));
    hotel.checkOut(ginger);
    Pet fluffy = Pet.rabbit().named("Fluffy");
    hotel.checkIn(fluffy);
    assertThat(hotel.getRegisteredPets(), 
               hasItems(fido,fluffy));
}

Things here are a lot more confused. We need to read it through carefully to understand what it is trying to test. There are several assertions at several points in the test, which makes it harder to see what the test is trying to demonstrate. And there are many reasons that this test might fail, which makes it harder to troubleshoot and debug.

Less is more

The test shown above would be easier to understand if we refactored it into two separate tests, following the Act-Arrange-Assert structure:

Pet fido = Pet.dog().named("Fido"); 
Pet ginger = Pet.cat().named("Ginger");
Pet fluffy = Pet.rabbit().named("Fluffy"); 

@Test
public void checking_in_several_pets() {
    // GIVEN 
    PetHotel hotel = new PetHotel();

    // WHEN
    hotel.checkIn(fido);
    hotel.checkIn(ginger); 

    // THEN
    assertThat(hotel.getRegisteredPets(), 
               hasItems(fido,ginger));
}

@Test
public void should_be_able_to_check_out() {
    // GIVEN
    PetHotel hotel = APetHotel.withGuests(fido, ginger, fluffy);

    // WHEN
    hotel.checkOut(ginger);

    // THEN
    assertThat(hotel.getRegisteredPets(), 
               hasItems(fido,fluffy));
}

Both of these tests are smaller and more focused, and only do one thing. As a result, they are easier to understand, and if they break you will have a better idea of why.

Conclusion

Arrange-Act-Assert is a good rule of thumb for both unit and acceptance tests. A few good things to keep in mind when using this pattern are:

  • Asserts are meant to demonstrate as much as they prove: like in mathematics, a proof is useless if the reader doesn’t understand it. Make sure your assertions make clear what you are trying to demonstrate.
  • One assert focuses, two asserts distract: the more assertion statements you have, the less clear it is what you are trying to check. If you have many related assertion statements, consider writing a custom matcher or method to group them together.
  • Arrange code can often be reused: if you are getting large blocks of code in your arrange sections, see if you can refactor it in a way so that you can reuse it for other tests.

What is your opinion on these 3 “A”s? Do you implement this technique in your test automation project?
Please let us know what you think in the comment section below!

John Ferguson Smart