20/06/2012

TDD in practice: Where does it fit in?

Lately I've been delving deeper into Domain Driven Design and Test Driven Development.

If you asked me two months ago; do you know and/or use test driven development? I'd say - yes, but I only write tests for algorithms or methods with expected results or behavior.

Since then, my view has changed.

The question I was asking was not how to do test driven development, but rather when?

Let's take a look at the well known rules for TDD set out by uncle Bob:
  1. You are not allowed to write any production code unless it is to make a failing unit test pass.
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
These are great rules. Too bad most developers don't know how to implement them.

It becomes pretty clear why, when we examine what I was implying two months ago.

We can only test against known results and behaviors.

We struggle to incorporate TDD into our coding practices because we are focused on code, instead of context.

Domain driven design gives a neat explanation of a domain; Whenever a context is implied, a boundary is formed.

Ubiquitous language implies that by classification, an appropriate name can be given for a domain entity that describes its context and purpose.


As an example, I'll describe an User within different contexts of healthcare.

A person that uses a system, is called a user.
A person that needs to be billed, is treated as an account. (Accounting)
A person who has clinical data, is considered a patient. (Clinical)
A user, may be a patient.
A user, may be a doctor.

Let's classify the above into User Roles (roles a user may fulfil) and Domain Entities
User Roles: patient, doctor
Domain entities: account, patient

The reason why we struggle to test domain entities is because we never define them.

Let's take a look at the average programmers model abstraction evolution:

In the beginning
UI -> Database

After we've learnt about presentation patterns
UI -> View Model/Presenter -> Database

After we've learnt about ORMs
UI-> View Model/Presenter -> Data Model -> Database

At this stage, another level of abstraction comes along if you're doing SOA
UI -> View Model/Presenter -> Data Transfer Object -> Data Model -> Database

Let's look at the last example in terms of context:

  • The UI defines the data of the View Model (or rather, the View Model contains the data for UI).
  • The DTO contains the data required by the View Model (the View Model also has UI specific properties like state unlike the DTO).
  • The Data Model represents the structure of how the object is stored in the database.
  • In a simple case, the DTO is effectively a partial 'view' of the Data Model.
The reason why we are struggling to practice TDD is because nowhere in this chain is anything represented in the domain (meaning we don't know where to apply the boundaries/rules).

We are effectively just mapping the one object to the other along the chain.

Let's add in the Domain Entity.
UI -> View Model/Presenter -> Data Transfer Object -> Domain Entity -> Data Model -> Database

What's interesting here is the fact that by simply adding the domain entity, we have given context to the entire chain. For example, the View Model/Presenter UI validation should mirror/conform to that of the Domain Entity.

The role TDD plays in DDD is that of verifying that the domain boundaries (or rules) are in place.

By defining boundaries, you create context. Context, in turn allows you to define behaviour which creates structure. Structure leads to reuse and better code.

Disclaimer: TDD in itself has many benefits and other use cases outside DDD.

In my opinion, the main reason why you should adopt TDD is because it takes the fear out of modifying and extending an existing system, because you can verify that everything works as it should.

We have learnt that just because a system builds successfully, doesn't mean it works the way it should.