The Three “A”s, building blocks for a great test suite
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!