The Three “A”s, building blocks for a great test suite

John Ferguson Smart | Mentor | Author | Speaker - Author of 'BDD in Action'.
Helping teams deliver more valuable software sooner4th July 2017

Writing high quality automated tests 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 is a common way of organising unit tests, and apply equally well to automated acceptance tests. Indeed, many real-world tests could be make 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 contest

- Act: perform the action we want to demonstrate

Assert: Show that the outcome we expect actually occured.

In Behaviour Driven Development, acceptance criteria are often organised in the same way, using the Given/When/Then format. The difference is one of levels of abstraction: Arrange/Act/Assert describe 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 what 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.

Interested in learning more? The Serenity Dojo Programme is a new online training programme designed for engineers in test who want to learn how to write top-notch test automation code using Java and WebDriver. Check out the programme here!

© 2019 John Ferguson Smart