Write clean and testable code with TDD (Test Driven Development)

Why should developers write tests, and how should they write it?

Ashis Jena
MiQ Tech and Analytics
6 min readApr 7, 2020

--

“Nothing makes a system more flexible than a suite of tests”
Robert C. Martin (Uncle Bob)

Clean Code :

Startups succeed owing to a multitude of reasons, but speed and efficiency are the primary drivers of a majority of these successes. The high level of collaboration and commitment towards a unified goal is how these are achieved. When a startup grows then their pace decreases. Only getting things done quickly is not sufficient. Getting things correctly matters. Because future growth depends on the quality, flexibility, and scalability of their products. To achieve this, you need to code correctly and cleanly.

At MiQ we understand the need for clean code. So steps like the continuous reduction of bug percentage, code coverage, product release predictability, and cost optimizations were included as part of the OKRs(Objectives and Key Results). To achieve these we adhered the TDD approach.

Code quality measurement — WTFs/minute (www.osnews.com/story/19266/wtfsm)

“Code like you deeply care for the code and the craft. You are the author of the code, and you will get readers, who will judge your craftsmanship”

Challenges :

How to reduce bugs and improve code quality?

Boehm’s Curve (https://www.nowsecure.com/blog/2017/05/10/level-up-mobile-app-security-metrics-to-measure-success/)

The cost of fixing bugs is exponential to the length of the feedback cycle.

Sooner a bug is identified and fixed, the lesser is the look-back time and better is the productivity.

The answer to the challenge was by strict quality control or testing.
Testing can be broadly categorized into manual testing and automated testing.

Manual testing is inefficient and costly which left us with automated testing.

Test automation can be further categorized into Unit testing (Developer testing) and Integration testing (Behavior testing).

Integration tests help to assert the behavior but fundamental challenges of achieving speed and flexibility do not improve with it. And this is where unit testing helps.

How to deal with legacy code?

This is a challenge that every team faces. They often don’t know how to improve the health of legacy code.

Legacy code is production-ready code and the fear of breaking it stops developers from doing any modifications to this. Without completely understanding the logic behind any code segment, writing tests for it is difficult and inefficient.

Teams maintain the legacy code as part of the BAU(business as usual) process. They do bug fixes and make changes in existing functionality due to changing business requirements etc. During this process, the developers who need to work on it have to understand the section of code and its surroundings where they have to do the code change.

This is the time when they must write the tests for legacy code. Tests should be written not only for the newly introduced code but also for the other surrounding code sections where they got a fair understanding during the implementation.

Some say they don’t get a change request on existing code for years, so how to improve the legacy coverage in such scenarios?

First, create a broader test suite that ensures functionality (ex: Integration test suite), and once you have some backing then start writing unit tests for chunk after chunk to improve the coverage.

Making developers understand the importance of unit tests!

Often younger or new developers are more focused on new technologies, languages, tools etc and ignore the fundamentals of software development i.e. code quality, modularity, loosely coupled, flexible, simple and easily readable code.

Some reasons often cited by few developers not to write tests are:

  • Writing unit tests makes their progress slow and they are on tight deadlines.
  • Testing is an uninteresting topic and should be left to testers.
  • We already write unit tests, so why should TDD be followed!

This needed a paradigm shift where TDD benefits had to be experienced by practice.

Writing tests after code, disadvantages :

  • Testing does not give direct feedback on design and programming.
    Most of the time, after realizing the functionality in code, unit testing is omitted.
  • Writing tests after developing code often results in “Happy Path” testing.
    We don’t do enough granular or “Testable” code segments to write the tests.
  • You would have spent a lot of time debugging to make the code work.

What is Test-Driven Development?

TDD is a software development practice that requires the test to be written first as per the feature or functionality requirements, and then writing minimum of code required to make the test pass.

Three laws of TDD :

1. You may not write production code unless you’ve first written a failing unit test.
2. You may now write more of a unit test that is sufficient to fail.
3. You may now write production code that is sufficient to make the failing unit test pass.

“Make it green, then make it clean!”

Why TDD?

  • With TDD you’ll code correctly, defect-free.
    Trivial mistakes are caught immediately, resulting in less time spent on rework.
    Tells you whether your last change (or refactoring) has broken previously working codes.
  • Less time spent in the debugger, resulting in shorter development time to Market. Increases the programmer’s productivity and cuts development costs.
  • Writing the tests first requires you to really consider what you want from the code. It helps you better understand the problem statement/ requirements before you jump to writing the code.
  • TDD helps create code that follows the SOLID principle.
    Forces you to write small classes focused on one thing.
    It results in cleaner Interfaces.
    Forces radical simplification of the code, you will only write code in response to the requirements of the tests.
    It allows the design to evolve and adapt to your changing understanding of the problem.
  • Most of the time it feels like a burden when you know your code works. It becomes customary after that to write tests that result in omitting scenarios.
  • TDD gives programmers the confidence to change the larger design of an application when adding new functionality.
    Without the flexibility of TDD, developers frequently add new functionality by virtually bolting it to the existing application without true integration.
    This can cause problems down the road.
  • Refactoring is a way to clean the code. When you are not confident with your tests, it gives you less confidence to refactor the code. Refactoring helps to write simple and easily understandable code.
Example of better readability

As shown in the code fragment, a programmer while reading the doSomething function no need to understand the senior citizen concession logic unless required. This code block can be abstracted by moving it to a method isEligibleForSeniorCitizenConcession

  • With TDD the unit tests are simple.
    It creates a detailed specification. i.e. self-documenting code.
    Since the TDD use-cases are written as tests, other programmers can view the tests as usage examples of how the code is intended to work.

TDD demonstration by example :

Implementation of the Hackerrank problem Bigger is Greater through TDD approach and using Java.

“As the tests gets more specific, the code becomes more generic”
Robert C. Martin

--

--