TDD: Context is King

So you want to start doing TDD huh? you have read all the blogs about it, seen all the videos on the web and now you’re ready to start. So you open your IDE/code editor of choice and start a new project and a new test project. And you just stare at it. You know what you have to do: write a test, make it fail, write the simplest code to make it pass, refactor the code to make it pretty. Rinse and repeat. The point is, you don’t really know what to test. Or how to know what to test. We all been there, blank face with just one thought: Where do I start?

So the main problem here is the lack of experience. I really can’t think of something else. I guess that’s not a problem when you’re doing TDD with someone who’s already experienced on this, but if this is not your case don’t worry, you just have to do one thing: start.

I guess it’s different for everyone but, I want to share some of my own personal practices on this matter. Hope this helps you getting started.

  1. Define the actions of the system.
  2. Define the business objects on a per use case basis.
  3. Write the test for only one business object first.
  4. Create interfaces for any dependency not defined yet.

Define the actions of the system

One of the most common pitfalls i have seen are the lack of scope and context. I know what i’m going to say sounds dumb but you really, really, really need to sit down and figure out what the system it’s going to do before writing some code. I just have seen so many people failing at this. My favorite technique is using UML use case diagrams. For starters i don’t really dig too much into the details, i just want to have general idea of the system scope and the users involved. A use case diagram let’s you see how the actors are going to interact with it and the specifics actions that a role/persona can do with it. When working on an agile environment i have found that these use cases map nicely with user stories, and even with epics if you’re using scrum. Just keep in mind that this is a high level view of the system. Don’t tangle too much with the details. Even better, don’t think about the details at all. Not a this time.

Define the business objects on a per use case basis

Once you have defined the uses cases for the system, go ahead and select one. Now you can go into the details. So from here i usually use an uml interaction diagram to define the way the objects will cooperate to accomplish the use case objectives. Resist the temptation to start sketching the implementation details: right now the only thing that you care about are the things that you request the object to do and the things that you expect to receive from it. In other words the objects I/O. Remember we are talking about business objects here, unless it’s required by the main flow, don’t put anything infrastructure related, i.e gateways to db, web services and other stuff that doesn’t have business logic but is meant to be used by other objects instead.

Write the test for only one business object first

Now you can start coding! First select the use case that you want to focus on. Now start by creating a file to contain the tests related to one of the related business objects. I adopted some conventions from the bdd movement and usually append specs to the file name. So let’s say i have a business object called BankAccount, the test file would be named BankAccountSpecs. And let’s say that this particular object has a method called Withdraw, then maybe i would create some tests called ShouldDecrementTheFundsOnWithdraw, ShouldFailWithdrawIfNotsufficientFunds and so on. Notice that i started all of my tests with the word “Should”. That’s another bdd adopted convention of mine. I like this one because it’s easier to express what the expected behavior is. In the end you have to remember that you are not testing, you are designing code. At least the implementation of the methods. Also don’t worry if while coding you decide to do things in a different way than that of the interaction diagram, this is part of the show too and it happens very often that an original good idea turns out to be overly complex at the moment of code it. Do not be afraid to try several approaches.

Create interfaces for any dependency not defined yet

If while coding you discover a dependency to an object not yet created, define an interface. Do not struggle trying to create an object to provide the service you need, this probably can be accomplished by another object later on. So just define the interface with the methods you need and use that. Don’t feel bad if you have interfaces with just 1 or 2 methods, this is fine. It’s also a side effect to TDD and a good one 🙂 (google for the interface segregation principle). Anyway now you need an object that implements the interface to be used in your code. You can hand code one. Or you can use a mocking framework. I strongly urge you to consider the later. You should not waste time creating objects for the sake of designing/testing your code. It really does not add value. Go ahead and take a look to any of the many mocking frameworks available for your platform of choice. Experiment until you find one you’re comfortable with and start using it.

Some last words (and warnings)

The purpose of everything that i have covered here it’s to help you getting started. As you keep getting the hang of it you may skip some of the steps outlined here. Maybe you will start creating tests that represent the whole use case instead of using a sequence diagram (i know i have). But if you are on the beginning line I strongly recommend you to follow the procedure I have described above. This will give you something valuable to start doing practicing TDD: Context. What to test? what not to test? where to start? that all depends on the context. Once you have the context set up, the answers to these questions will flow naturally. The rest of the procedure you have already learn from the books, podcasts and posts such as this one: Green, Red, Refactor. Also i have a word of caution here: do not try to write all of the tests before hand. Don’t think of moving from use case to use case gathering all of the use cases to start writing them off later on. This is a bad idea. The reason is simple: you are designing code, not writing tests. Tests are only a tool to design in as much as paper and pen. And when designing code, there are several decisions that have to be made when something unexpected arises from the code. Also some tests will lead to the creation of another tests.

So there you have it. Stay tuned, more on this coming on.