It’s a well established within any good software engineering shop that you need tests to write good code. Once you get your developers on board, the next step is to start documenting the context of those tests. Good naming conventions, and a practice of starting with the simplest unit first will help you in writing those tests, but it won’t explain why they are there. This is especially true when writing unit tests for objects with complex dependencies.

Let’s say you are presented with the following unit test;

<?php

public function testGroupOfUsersLoadByIdWithNullReturnsNull() {
  $user = new User($this->db, null);
  $wrapper = new UserWrapper($user);
  $group = new GroupOfUsers(array($wrapper));
  $response = $group->loadUserById(null);
  $this->assertNull($response);
}

Before reading further, can you name which method are we looking at? Does it matter that User was configured with a null argument? Does it matter that the group object only has one user in it? Is it just returning the second argument of the first line?

To make this easier, we use a notation called “Given, When, Then”, or GWT.

This is the same test documented with GWT notation.

<?php

/**
 * Given a GroupOfUsers object loaded with a group of valid users,
 * When loadUserById() is called with a null argument,
 * Then a null will be returned.
 */
public function testGroupOfUsersLoadByIdWithNullReturnsNull() {
  $user = new User($this->db, null);
  $wrapper = new UserWrapper($user);
  $group = new GroupOfUsers(array($wrapper));

  $response = $group->loadUserById(null);
  $this->assertNull($response);
}

In this example, by specifying that User objects are valid in the Given part of the doc, we make it clear that it’s part of the context, but not the system under test. If we come up with a new method of building User objects, we know that we can happily rewrite those lines. You might notice that I’ve also separated out the last two lines with a newline. This is a convenient short hand to indicate that these are the important lines of the test, namely the execution of the event indicated in When statement and the assertion of the expected outcome in the Then statement.

If you’re doing test driven development, then you may find that it’s easier to write the GWT before the test code itself. In the same way that writing a test first lets you separate how the behaviour of a component from it’s implementation, writing the documentation before the test itself lets you describe the expectations without worrying about how to implement the test. This is especially useful for tests involving complex object trees. In the above example, writing the GWT first may lead you to consider that returning a null is not the best behaviour. It’s easier to rewrite the GWT than to rewrite your tests. Depending on the system, you may want to write out several tests in GWT format before writing any tests at all.

Finally, writing the GWT first helps break down unit tests into logical units. Some tests will have multiple statements to indicate the context of the system, indicating by adding statements to the When line with the And keyword. Likewise, multiple assertions can be made about a result by adding them to the “Then” line.

<?php

/**
 * Given a UserController object,
 * When addUser() is called,
 *  And a userid is passed in as an array element,
 *  And a fullname is passed in as an array element,
 * Then a User object will be returned,
 *  And the object will have an integer userid,
 *  And the object will have a name set for it's fullname.
 */
public function testUserControllerCreatesUsersObjects() {
  $controller = new $this->masterFactory->createUserController();

  $response = $controller->addUser(array('userid' => 555, 'fullname' => 'Adam Smith'));

  $this->assertInstanceOf(\Acme\User::class, $response);
  $this->assertEquals(555, $response->getUserId());
  $this->assertEquals('Adam Smith', $response->getFullname());
}

If the unit test doesn’t fit this structure, it’s a code smell that you need to break up the unit test.


See also; Introducing BDD, from Dan North & Associates