Growing Object-Oriented Software, Guided By Tests (chapter 21)

Feb 24, 22

TDD can lead to an initial productivity boost as it is used to drive development. However, the pace may slow down once the test suite needs to be maintained.

Tests must be readable to ensure they are maintainable.

Test code should describe what production code does in a declarative fashion.

Follow these tips to make the test code more readable:

Test names should describe features

Name tests in terms of the features that the target object provides. Use codeDOX to help extract which behaviour you are testing - e.g

codeDOX = “A List holds items in the order they were added.”

Example test:

public class ListTests
{
	[Fact]
		void holdsItemsInTheOrderTheyWereAdded()
		{
			// < test code goes here >
		}
}

Think in terms of what the object does, rather than what it is.

The name of the test should also express the expected behaviour.

Should we name the test first, or write the test first? Use whatever works for you! As long as you follow through and make sure the test is consistent and expressive.

How to structure a test

We should strive to write tests in a standard format as this will make it easier for us to parse later when we need to debug/change the code.

The authors point to these ways to structure a test.

  1. Setup - Prepare the context/environment of the test.
  2. Execute - Call the target code, triggering the behaviour under test.
  3. Verify - Check if the result matches your expectation.
  4. Teardown - Clean-up state that may corrupt any further tests.

I have also heard these steps being called:

  • Arrange
  • Act
  • Assert

Which I think is a little easier to remember ;)

We should try to write tests ‘backwards’, that is:

  1. Write the test name.
  2. Write the call to the target code.
  3. Arrange the setup and teardown.

This should help us to stay focused.

We should try and streamline the test code as much as possible. Move noisy parts of the test setup to helper methods to help make each test easier to parse. However… Be careful not to abstract away too much of the test code, as this will also make it hard to reason about when trying to maintain the test suite.

If a test requires a lot of setup it might be viable to create a new object just for testing. Again, be sure not to abstract in the test code too much - the main thing is that our tests stay readable.

Assertions and expectations should communicate exactly what matters in the behaviour of the test code.

When using values in tests, give them a name by assigning them to a variable. This will make what the value represents explicit, rather than being a ‘magic’ value.