About TDD

Lot of conversation happened around TDD recently. For someone who has not practiced TDD it will be hard to grasp what it portrays. Firstly, it’s not writing tests after writing the logic. There are things that go beyond it.

Personally, TDD has helped me much beyond writing tests or attaining code coverage, it has helped me improve in my engineering role.

TDD starts with identifying the test cases for the logic that we are going to implement. Then we add the test cases and they will fail as of that moment (obviously!) and the we go on to implement the logic.

Although, this might sound simple, there is a lot going on to reach here. This is where the whole idea differs from writing test cases after implementing logic. There is another point to be discussed here about unit tests. This will be covered later on. For now, let’s go back to test cases.

When identifying the test cases, we treat the logic that we are going to implement as a whole. We are not concerned about how many methods are we going to split the logic into inside the module or class. We are not concerned about any other implementation detail inside. Hence, fair to say we treat the whole logic as a black box. What we are concerned is about the input values that we provide and the return values we get. (Testing side effects is another related aspect discussed later on). This is an important shift in thinking for someone who is not practiced with TDD but writes test post writing the implementation.

At this point we are stuck with two questions

  1. How do we proceed or write specs if we are not sure about the implementation logic and side effects? (considering we treat it as a black box)
  2. How do we identify the test cases?

Let’s discuss point number two.

Identifying the test cases is the vital part of TDD. Implementation follows. Our goal is to solve a problem and we can do it efficiently when we understand what are the possible scenarios associated with that problem. The core part of the problem could be trivial, consider a file upload. With the right libraries and credentials in place it becomes a matter of couple of method invocation of the library.

That solves our requirement, but does it solve all aspects of the problem?

This is where for me, personally TDD brings about the changes. We start to think about what all could happen here and how our program should behave during those scenarios.

For eg:

  1. What to do when the third party service is unavailable?
  2. What if the file is not available?
  3. Is the file in a supported format?
  4. What needs to be done once the activity is done?

We can keep adding to the list above depending on the context. You got the idea — we start to think about the whole process and not the requirement. This is what makes the whole activity different.

As a developer we are solely focused on the programming and implementation part. We are focused on implementation that we go ahead and get that done first.

Hey, tests are written before committing it, it’s the same right?

Going back to the list that was added above, it is possible that an experienced engineer will consider all that while doing the implementation. There is no denying that. There is no claim that without following TDD that the engineer will not think about all this. That’s not the point here. One could do the implementation first and then add all the tests and things could be well done. If one is a strong proponent of that and will not budge to change, then there is no pressure. Follow what works for you, although here we discuss how we can use up TDD to bring about another approach to the whole activity.

One of the best advice I ever got was to think enough and draw out all possibilities before starting to implement. In fact, implementation should be among the last part of the whole activity and needn’t be what time is spend on - thinking and drawing out the solution is. This is where TDD aligns for us.

With a problem at hand we can think of all the possible scenarios and start writing specs for it. To do this activity seamlessly we would need to understand the problem in depth, we would need to understand the related context. Possible out comes and side effects. How other systems would behave or use our implementation. At this point we are no longer bothered about the implementation detail rather than the usage and effectiveness of the solution. This is what a major part of engineering activity is. On top of that we are now thinking from a product perspective.

One of the biggest advantage of Software Engineering is that we are allowed to iterate and improve unlike other fields like say for example Civil Engineering.

We shall think of all the scenarios and use cases and start writing them as each test case. This also enables us to define the program clearly - because we are explicitly defining the behaviour one after the another. This also gives us immense clarity on what needs to be done because after this activity we have clearly defined the expectations and no surprises awaits us. Implementation now becomes a pretty straightforward task.

Most of the delays in implementation are either from unknowns which were unexplored or delays caused by change in scope

Personally, TDD is not just about writing tests. This approach helps analyse the requirement from a higher view. Requiring to gather more information and clarify requirements to an extent that we are sure of what is being done from a product perspective and not just about the class/module behaviour that we are going to implement.

It has tremendously helped to improve how a task is approached, TDD being a part of it. To have clarity and reasoning documents are also created (Again, to be discussed later), specs are written and finally implementation is done. There is no surprises or unknowns and most of sleep peacefully.