QWAN

TDD Heuristics: Start with the expectation

Tags: test driven development, feedback, eXtreme Programming,

Write the last part of your test first: start with the expectation (or the assert) and write the test bottom-up.

Starting the test at the end, where the expectation (or assert) is may feel strange. You may be inclined to write your test from top to bottom, from set-up, through invocation of production code, to expectation. Starting with the expectation may feel the wrong way around.

start with the expectation, person holding a finish flag

Think of it like writing a report - you might remember having to write reports when you were at school: a good approach is to first write up the conclusions, and then write the rest of the report leading to those conclusions. Writing a test by starting with the expectation works in a similar way.

Focus on outcomes

Focusing on the expectation first means focusing on the desired outcome. Sometimes this is easier to express than the set-up code needed. You will also notice duplication in set-up code more clearly.

Starting with the expectation is harder than just copy-pasting the previous test and changing some lines. It forces you to think of desired behaviour and interfaces right from the beginning. You suddenly have to state clearly what you are working towards.

From the expectation we work backwards to complete the test. We write the minimally required set-up and code to get to our outcome.

Example

Let’s look at an example from our Online Agile Fluency® Diagnostic application. This application supports a facilitator in running a workshop with a development team. In the code, we represent such a workshop by a concept DiagnosticSession.

We would like to enable co-facilitation of workshops and add a sharing feature to the DiagnosticSession class. So how do we start? Create a session, do something with it? No, let’s start with the expected outcome: a shared session is accessible for the co-facilitator:

1
2
3
def test_sharing_makes_it_accessible_for_the_secondary_facilitator():

  assert_that(session.is_accessible_for(co_facilitator), is_(True))

Then we can add the behaviour under test:

1
2
3
4
def test_sharing_makes_it_accessible_for_the_secondary_facilitator(self):

  session.share_with(co_facilitator)
  assert_that(session.is_accessible_for(co_facilitator), is_(True))

And finally, we add the set-up code:

1
2
3
4
5
def test_sharing_makes_it_accessible_for_the_secondary_facilitator(self):
  session = aValidDiagnosticSession(facilitator_id=aValidID('99'))
  co_facilitator = aValidFacilitator(id=aValidID('44'))
  session.share_with(co_facilitator)
  assert_that(session.is_accessible_for(co_facilitator), is_(True))

aValidDiagnosticSession and aValidFacilitator are examples of test data builders.

Effects

We force ourselves to state our intent first, which helps in thinking about design. This provides a low-cost, fast feedback loop, helping us to get clear where we want to go.

It is ok to have it feel wrong and awkward. Starting with an expectation, we inject a little deliberate practice in your day to day work.

Focusing a test on the outcome, helps to reduce wandering tests, clutter, and unnecessary set-up code. We create tests that go straight to their goals.

Legacy code

You may believe that this does not apply to legacy code. Quite the contrary: although it can be harder to state the outcome, it will help you identify better, less cluttered, and more focused interfaces for existing code. The initial investment may be big, but so is the feeling of increased understanding and relief after you’ve done it.

Further reading

The idea of starting with the expectation comes from Kent Beck’s Test Driven Development, By Example; he describes a pattern called Assert First.

jMock, the first mock object library, contributed to this way of thinking. It forced us to write expected calls as the first thing in the test. This was counter-intuitive at first, but it did force us to think about the interactions we expect, before we write code to set up the test.

Here is a snippet from Mock Roles, not Objects by Steve Freeman, Nat Pryce, Tim Mackinnon, and Joe Walnes, to illustrate the idea:

1
2
3
4
5
6
7
8
9
public class TimedCacheTest {
  public void testLoadsObjectThatIsNotCached() {
    // we expect to call load
    // exactly once with the key,
    // this will return the given value
    mockLoader.expect(once())
      .method("load").with( eq(KEY) )
      .will(returnValue(VALUE));
    ...

In effect, jMock forced us to work Then-first, so instead of the usual pattern Given-When-Then, we get Then-When-Given or Then-Given-When, or in case we don’t need much set-up, just Then-When. We’re thinking first about desired interactions, how the API should be named, what parameters to pass in, what to return, and who to interact with.

Thinking more outside in, we often use Chris MattsIn order to <achieve some value> story format, as described by Elizabeth Keogh in RIP As a… I want… So that…:

1
2
3
In order to <achieve some value>
As a <role>
I want <some feature>.

Here, we also start with (high-level) expectations.

This is a post in our series on Test Driven Development.

Subscribe to our RSS feed