TDD: Perspective Renewed — another value

Jonathanjojo
7 min readMar 8, 2020

--

Test Driven Development, a stumbling block for many, a supporting block for more. Is there more to it?

This article is written as a part of individual review criterion of PPL CS UI 2020

Overview

At this point, I am sure you already know about TDD: the act of making tests prior to the corresponding implementation — following the below cycle.

cheescakelabs.com

The tests cover the scenarios that would be expected to be run on the system. So when the implementations are being developed, we can be sure that all and only the relevant & tested scenarios are covered.

Is it all there is to it? With this post, we will unravel yet another benefit that is important to me as a developer.

The pitfall for (some) developers

When TDD is on your agenda, you will have to spend some of your time designing and coding the tests first (then to the implementation). But first, take a look at what can TDD give you:

Advantages

Below are some advantages that TDD could bring to your team and project:

Function Modularity As you design the tests first, you will be forced to design a modular function that can be tested on itself. In the end, this will promote modularity throughout your project and leads to cleaner code and easier debugging.

The test
a part of the implementation that is created to pass the test

Cases Handling & Case-based Design Thinking — The tests should be designed to cover context-relevant scenarios, including edge cases. This way, your implementation would cover the cases (and those cases only) for it to pass the tests. To add to its value, it also serves as documentation by listing all the scenarios that can be handled.

Scenario-based test

Continuous Delivery Confidence when we push new features knowing that they are already tested and passed the test, we will have more confidence for it to work properly. This way, developers can deliver work faster and with utmost confidence (as long as the tests are relevant). As the code grows big, this will ensure the code still works.

Now you can be sure that this branch is safe to merge

Disadvantages

Unfortunately, TDD comes with some costs:

More codes, way more — as feature grows, the test grows as well. Even more, some tests are harder to pull off than the implementation itself. For one simple conditional, there would be 2 test cases, and so on. This is why sometimes the tests can be longer than the implementations. Especially when you are forced to have 100% code-line-coverage.

Not to mention that it is particularly cumbersome for early developers to write tests before the implementations, it might need some time and changes.

In our case, the tests are almost 4-times fold the amount of the corresponding implementation. I know right.

Even more codes along with changes — And, if you have some changes to your feature, you have to rewrite the tests first. This should be enough explanation.

These cons are the stumbling block for early developers. They might find themselves as good programmers who almost never make mistakes, especially when it comes to not so large projects. Or, they have close friends as developers who they trust so much not to make any mistakes. Or, they find themselves as a lone programmer on a single project who doesn’t need anyone else, they can solve any bug that will come will be able to be solved by themselves.

Even us doubted the use of TDD at first. In the end, why would we bother to use TDD?

Is (correct) TDD really mandatory

It was never a must-have, nor a best-practice. It was a tool. And as a tool goes, it only gets as good as the user, and it only gets as useful as the context being used.

The benefits of TDD will take you far. Regression endurance, cleanliness, documentations, and well-thought scenario-based implementations, etc will definitely bring some benefits to your projects. Our tests describe what can our code do and what it cannot:

What the program accepts / can do, and what the program does not accept / cannot do. Reading it will tell the new developers a “sneak peak” to the code without the need of digging deep into the implementation.

These advantages are proven to be handy, as developers switch roles/repo/domain, we refer to the tests to understand what the current codebase is able to do and is not able to do. Do you have problems that the above advantages will solve? For example, if you have:

  • Long-running and non-personal projects.
  • A team of developers who would deliver work individually and then merge it together.

If no, then you probably need some specific reasons to back up your decision to use TDD at all. Continue to read if you need more push on what other benefits TDD can give you.

If yes, make sure you do it properly.

Two Important Lessons

I used to think that coverage and test are everything. A pitiful mistake, below are 2 lessons I learned the hard way, so you don't have to:

  1. You should aim for 100% feature coverage, which is: given the business context, all the scenarios are handled, NOT 100% line coverage. Line coverage is there to ensure all things are being tested, and only all that is tested (used for real) is there. If you find your developers test features trivially just to get that 100% line coverage, you may want to reflect on the incentive given to them by doing such tests. Remember your aim is to handle scenarios, not all holes in your code. Your code should reflect on the scenarios.
  2. Context is everything, without it, the tests are not relevant. Tests that were made just for line coverage are hardly useful since no real scenarios would be even represented (heck, without context, the implementation would be questioned too). It is a must to have your developers understand the business cases involved in the project and to understand its relation to the code.

[BONUS] A little remedy to the lengthy test code

Still, the lengthy code of tests is still there. We use factories for our tests, which produces objects that we need. At the very least, we save time and effort on creating testable objects (although the tests are still lengthy, unfortunately).

Faker and Factory (DJangoREST implementation)

Now we can use them like this:

self.user_1 = UserFactory(username="user_1")
self.admin = AccountFactory(admin=True, user=self.user_1)

Does this violate the essence of TDD? No, in my opinion. It does not reduce any comprehension for the developers reading the tests as well as it does not reduce the coverage of the tests. They can always refer to the factories and the factories are based on real models.

A new perspective

Now, I wish we can share a new perspective on TDD.

As I said, TDD is nothing but a tool, it is as good as its user and the context being used. Yes, it is good to have TDD in your arsenal due to the benefits mentioned above. But there is a catch.

Tests do not improve the quality of the code, the developers do.

Do not think the tests are made just to ensure the code runs well, but think that the tests are also made so that to ensure the developers understand the business context behind the code — in the end, to ensure the developers fully grasp the essence of the code itself: why is it there, where it should be.

As the Agile Manifesto elegantly stated, we shall value the developers’ interaction with the context from users (and to the code), more than we value processes and tools.

This is why in our team, we value TDD more. It gives us a glimpse of the targetted final product of the current iteration. Our developers have to understand the context first before they start to code, and gladly, TDD came just in the right place and time.

TDD in action by our team

That is all for now, have fun and take care!

References

--

--