Chapter 2. Test-Driven Development with Objects

Music is the space between the notes

--Claude Debussy

A Web of Objects

Need to put something in about values vs objects (computation).

Object-Orientation is more about the communication between objects than it is about the objects themselves. As Alan Kay [Kay1998] wrote:

The big idea is "messaging" […] The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.

An object is a computational process that communicates by messages: it receives messages from other objects and reacts by sending messages to other objects and, perhaps, returning a value or exception to the original sender. An object has a method of handling every type of message that it understands and, in most cases, encapsulates some internal state that it uses to coordinate its communication with other objects.

An object-oriented system is a web of collaborating objects. A system is built by creating objects and plugging them together so that they can send messages to one another. The behaviour of the system is an emergent property of the composition of the objects: the choice of objects and how they are connected.

A Web of Objects

Figure 2.1. A Web of Objects


This lets us change the behaviour of the system by changing the composition of its objects—adding and removing instances, and plugging different combinations together—rather than writing procedural code. The code we write to manage this composition is a declarative definition of the how the web of objects will behave. It's easier to change the system's behaviour because we can focus on what we want it to do, not how.

Follow the Messages

We can benefit from this high-level, declarative approach only if our objects are designed to be easily pluggable. In practice, this means that they follow common communication patterns, and that the dependencies between them are made explicit. A communication pattern is a set of rules that govern how a group of objects talk to each other: the roles they play, what messages they can send and when, and so on. In languages like Java, we identify object roles with (abstract) interfaces, rather than (concrete) classes—although interfaces don't define everything we need to say.

In our view, the domain model is in these communication patterns, because they are what gives meaning to the universe of possible relationships between the objects. Thinking of a system in terms of its dynamic, communication structure is a significant mental shift from the static classification that most of us learn when being introduced to objects. The domain model isn't even obviously visible because the communication patterns are not explicitly represented in the programming languages we get to work with. We hope to show, in this book, how tests and mock objects help us see the communication between our objects more clearly.

Here's a small example of how focusing on the communication between objects guides design:

In a video game, the objects in play might include: actors, such as the player and enemies; scenery, which the player flies over; obstacles, which the player can crash into; special effects, such as explosions and smoke; and head-up display elements such as the score and number of lives. There are also scripts behind the scenes spawning objects as the game progresses.

This is a good classification of the game objects from the players' point of view because it supports the decisions they need to make when playing the game, when interacting with the game from outside. This is not, however, a useful classification for the implementors of the game. The game engine has to display objects that are visible, tell objects that are animated about the passing of time, detect collisions between objects that are physical and delegate decisions about what to do when physical objects collide to collision resolvers.

As you can see in Figure 2.2, “Roles and objects in a video game”, the two views, one from the game engine and one from the implementation of the in-play objects, are not the same. An Obstacle, for example, is Visible and Physical, while a Script is a Collision Resolver and Animated but not Visible. The objects in the game play different roles depending on what the engine needs from them at the time. This mismatch between static classification and dynamic communication means that we're unlikely to come up with a tidy class hierarchy for the game objects that will also suit the needs of the engine.

Roles and objects in a video game

Figure 2.2. Roles and objects in a video game


At best, a class hierarchy represents one dimension of an application, providing a mechanism for sharing implementation details between objects; for example, we might have a base class to implement the common features of frame-based animation. At worst we've seen too many codebases (some our own) that suffer complexity and duplication from using one mechanism to represent multiple concepts.

For a discussion of designing in terms of Roles, Responsibilities, and Collaborators see [Wirfs-Brock2003].

Tell, Don't Ask

Given that we have objects sending each other messages, what do they say? Our experience is that the calling object should describe what it wants in terms of the role that its neighbour plays, and let the called object decide how to make that happen. This is commonly known as the “Tell, Don't Ask” style or, more formally, the Law of Demeter. Objects make their decisions based only on information they hold internally or that came with the triggering message, they avoid navigating to other objects to make things happen. Followed consistently, this style produces more flexible code because it's easy to swap objects that play the same role. The caller sees nothing of their internal structure or the structure of the rest of the system behind the role interface.

When we don't follow the style, we can end up with what's known as “train wreck” code, where a series of getters are chained together like the carriages in a train. Here's one case we found on the Internet:

((EditSaveCustomizer) master.getModelisable()
  .getDockablePanel()
    .getCustomizer())
      .getSaveItem().setEnabled(Boolean.FALSE.booleanValue());

After some head scratching, we realised that what this fragment meant to say is:

master.allowSavingOfCustomisations();

This wraps all that implementation detail up behind a single call. The client of master no longer needs to know something about the structure of every type in the chain. We've reduced the risk that a design change might cause ripples in remote parts of the codebase.

As well as hiding information, there's a more subtle benefit from “Tell, Don't Ask”. It forces us to make explicit and name interactions between objects, rather than leaving them implicit in the chain of getters. The shorter version above is much clearer about what it's for, not just how it happens to be implemented.

Of course we don't “Tell” everything (although that's a very interesting experiment to try to stretch your technique). We “Ask” when getting information from value objects and collections, and when querying data sets.

Unit Testing Collaborating Objects

We appear to have painted ourselves into a corner. We're insisting on focused objects that send commands to each other and that don't expose any way to query their state, so it looks like we have nothing available to assert in a unit test. For example, in Figure 2.3, “Unit testing an object in isolation”, the circled object will send messages to one or more of its three neighbours when invoked. How can we test that it does so correctly without exposing any of its internal state?

Unit testing an object in isolation

Figure 2.3. Unit testing an object in isolation


One option is to replace the target object's neighbours in a test with substitutes, or Mock Objects, as in Figure 2.4, “Testing an object with Mock Objects”. We can specify how we expect the target object to communicate with its Mock neighbours for a triggering event; we call these specifications Expectations. During the test, the Mock objects assert that they have been called as expected, they also implement any stubbed behaviour needed to make the rest of the test work.

Testing an object with Mock Objects

Figure 2.4. Testing an object with Mock Objects


With this infrastructure in place, we can change the way we approach TDD. Figure 2.4, “Testing an object with Mock Objects” implies that we're just trying to test the target object and that we already know what its neighbours look like. In practice, however, those collaborators don't need to exist when we're writing a unit test. We can use the test to help us tease out the supporting roles our object needs, defined as Java interfaces, and fill in a real implementations as we develop the rest of the system. We call this Interface Discovery; you'll see an example when we extract an AuctionEventListener in Chapter 8, Getting ready to bid.

Support for TDD with Mock Objects

To support this style of test-driven programming, we need to create Mock instances of the neighbouring objects, define Expectations on how they're called and then check them, and implement any stub behaviour we need to get through the test. In practice, the runtime structure of a test with Mock Objects usually looks like Figure 2.5, “Testing an object with Mock Objects”.

Testing an object with Mock Objects

Figure 2.5. Testing an object with Mock Objects


We use the term Mockery for the object that holds the context of a test, creating Mock Objects and managing Expectations and stubbing for the test (the name is a pun from Ivan Moore that we adopted in a fit of whimsy). We'll show the practice throughout Part II, “A Worked Example”, so we'll just touch on the basics here. The essential structure of a test is:

  1. Create any required mock objects

  2. Create any real objects, including the target object

  3. Specify how you expect the mock objects to be called by the target object

  4. Trigger the event(s) on the target object

  5. Assert any resulting values and that that all the expected calls have been made

The unit test makes explicit the relationship between the target object and its environment. It creates all the objects in the cluster and makes assertions about the the interactions between the target object and its collaborators. We can code this infrastructure by hand or, these days, use one of the multiple Mock Object frameworks that are available in many languages. The important point, which we stress repeatedly throughout this book, is that we try to make clear the intentions of every test: what's tested functionality, what's supporting infrastructure, and what's object structure.