Test-Driven Development and The Power of Self-Validating Code

Georges Lteif

Georges Lteif

Software Engineer

Last Updated on March 28, 2022.
Subscribe now to stay posted!
About Us
7 min read

1. Overview

Test-Driven Development, or TDD, is a discipline that advocates writing unit test cases before any production code.

While some influential people in the software community are fully committed to TDD, others have declared it dead.

In this article, we examine Test-Driven Development from a micro and macro point-of-views.

The examination at the micro level will allow us to see the immediate advantage (as well as caveats) of TDD. In these sections, we look at how TDD is supposed to work, what challenges it faces, and what opportunities become available.

On the other hand, the macro examination will show us how instrumental TDD can be when trying to improve your SDLC processes, especially when it comes to optimizing resource usage and shortening the delivery timeframes while preserving quality.

Examining TDD would be part of an overall effort to achieve Operational Excellence, the term that combines quality, efficiency, and flawless execution.

2. Table of Contents

3. TDD – The Micro Examination

3.1 What is TDD

Test-Driven Development is a discipline in software development that consists of writing failing unit tests before developing the actual production code to support them.

A Word of Caution on Unit Testing

TDD practices often include the writing of unit tests as the primary type of software testing tools to be used. We have argued at length the utility, or lack thereof, of unit testing in certain situations, particularly when “unit” is very small (class or function level).

For the purpose of this article, when we use the term “unit testing”, we are generally referring to either Unit Tests or other types of tests, such as System Integration Tests, depending on applicability.

I found that Bob Martin‘s definition of TDD in the form of three laws to be quite useful. This is what we will be using here as well:

Law 1

You are not allowed to write any production code unless it is to make a failing unit test pass.

Law 2

You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.

Law 3

You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

3.2 Advantages of Using TDD

TDD presents three distinct advantages.

#1 Avoid Wasting Effort Debugging Your Code

Debugging is an effort-intensive and time-consuming process. You should not do it if you can help it.

This is a direct implication of the three laws where you are not allowed, by design, to write any code that needs heavy debugging.

The rationale is as follows: if your code was working one minute ago, all you have to do to debug it would be to undo those last few changes. In fact, you don’t have to use the debugger at all.

Granted that does NOT mean you will never ever have to use the debugger; it just brings its usage to a minimum.

#2 Decent Battery of Tests

You have completed writing your production code and now you have decided to write some unit tests because only its part of the process. Evidently, your heart will not be into it (since you know the code is already tested and working). TDD helps you avoid that.

A battery of test cases that is incomplete, full of holes, or with less-than-ideal test coverage adds little value to your development practices. Passing those tests means very little.

On the contrary, having a decent battery that can give you enough confidence to release into production is quite essential for improving your Agile practices.

Another, equally important advantage of having decent testing capabilities is that you can keep your code clean by regular refactoring exercises.

Clean code means running a well-oiled machine. Bad code will slow you down until the cost of ownership becomes too prohibitive.

#3 Designing Testable Code

Using TDD can push developers to design and develop code that can be easily tested. This is somehow a natural consequence of the three laws of TDD.

Ideally, you want all your code to be testable so that you can pass that task to an test automation framework. Manual testing is one of the primary sources of waste that can be easily eliminated.

3.3 Challenges of Using TDD

TDD, when incorrectly implemented, will present some daunting challenges.

#1 Cost of Ownership

A growing body of code, perhaps a few times the size of the production code, whose sole purpose is to test another body of code, will introduce a hefty price in terms of cost-of-ownership.

Even when the unit tests are exquisitely designed and extremely robust, they still require effort to create and maintain, effort that can be redirected to develop code that customers are happy to pay for.

#2 Time-to-Market

The outcome of Test-Driven Development, when coupled with badly designed software testing strategies, is a body of test scripts that nobody wants to maintain. This can become a major drag on your project momentum, i.e. lower-profit and slower time-to-market.

A poorly-implemented test strategy can slow you down considerably. You can check the list below for signs of a crappy implementation:

  1. Slow execution. Ideally a full test suite should complete in a few minutes, otherwise its unusable.
  2. Testing implementation rather than functionality. This happens when you couple code that is designed for testing your software with code from production.
  3. Plenty of mocking (ideally you want to run a test on an environment that is as close to production as possible). To make things worse, mocking seriously couples your test code with the production one.

3.4 Taming the Challenges

To mitigate these problems, you can engineer your test strategy and automation framework in a smart way.

The following guidelines to implementing a powerful test strategy can be applied:

  1. Use unit testing ONLY where applicable. This means redefining the “unit” in “unit testing”.
  2. Avoid mocking (databases, external systems, filesystems, etc.) at all costs.
  3. Test functionality, never implementation. This is otherwise known as Behaviour-Driven Development.
  4. Design a simple, robust, and automated framework leveraging existing tools on the market. Look at DevOps practices for inspiration
  5. Design code that can be easily tested (more on this later).

In summary, the challenges presented by a poor implementation of TDD are neither unavoidable nor unsurmountable.

4. TDD – The Macro Examination

Test-Driven Development is a discipline that will allow us to glue together some of the practices designed to reduce waste in the development and testing stages, waste reduction being the number one target of Operational Excellence.

The next subsections will explain how this can be done.

4.1 Streamlining Development and Testing Efforts

The traditional approach to testing new code invariably involved QA having to wait until all the development (and its testing) have completed before carrying on with any their activities.

It is true that during this time, testers usually occupy their time with preparing test plans, perhaps even writing down (or creating) the specific test cases.

All that is slow admin work, and times are much less stressful until actual testing starts.

During the testing period, the development team usually takes an informal break, waiting testers to uncover any bugs.

In fact, the sequential nature of development and testing can be major source of waste and the only way to eliminate it is by a creative redistribution and reorganization of testing activities.

This is where TDD comes in. Have a look at the below diagram.

test driven development tdd
Test-Driven Development in Operational Excellence

The solution we propose is as follows:

Stage 1

Once the business requirements have been signed-off by the internal and external stakeholders, developers and testers start writing test cases. Test cases from both parties are integrated together in a seamless fashion, typically through Source Code Versioning tools.

Stage 2

Developers using Test-Driven Development practices start writing production code and using the test cases generated in Stage 1 to validate their changes. Testers simultaneously enhance the test suites created by the developers and integrate them together.

Stage 3

At the end of the development cycle, the production code is finalized and all the test cases are now passing. The Development AND Testing stages would are simultaneously concluded.

4.2 Embedding Quality Controls in the Delivery Process

Operational Excellence involves a relentless drive for better quality products. This squares nicely with the way Test-Driven Development works.

To illustrate how that works, here is a small example.

You can include a step in the development and testing processes that says the following: Once a bug has been found, it is required that the developer or tester who uncovered it, to add a test case that tests that specific scenario.

The developer can then patch the code to make that test case pass. Once it’s done, there is no need to test it again.

A release can be deployed into production ONLY when it passes the full suite of regression tests, and your test framework should provide a report to that effect.

4.3 Continuous Improvement

The first version of any code that you write is usually crap. Everybody knows that. Everybody equally acknowledges the necessity of getting things done, so you can get away with it the first time.

Sometime later, perhaps when trying to extend that code, you come to think that there might be a better way of writing it. This process is called refactoring and is really what keeps your code healthy.

No developer will have the courage to refactor any code unless she is confident that there is a battery of test cases that can easily uncover any broken features.

Continuous improvement requires that confidence to be present, and the latter depends on how well you apply TDD.

5. Conclusion

Test-Driven Development can be a powerful discipline when applied properly, with the right tools.

In fact, TDD is essential for achieving Operational Excellence by A) ensuring an optimal distribution of resources, B) making the most of their limited time, and most importantly C) staying on top of your quality issues.

DO NOT religiously follow procedures (such as testing internal classes and mocking databases) which were invented later on by people who perhaps did not understand the author’s views as he meant them.

These serve no purpose but to slow you down until you finally give up on TDD.

6. Further Reading

Two great talks on the subject.

Uncle Bob Martin on TDD
Challenges of TDD by Ian Cooper

Technical Risk Management and Decision Analysis — Introduction and Fundamental Principles

1. Overview I could not find a better way to start an article on Risk and Risk Management than by quoting the opening lines of Donald Lessard and Roger Miller’s 2001 paper that, briefly but lucidly, summarizes the nature of large engineering endeavours. It goes like this: This article leans heavily on three handbooks thatContinue reading “Technical Risk Management and Decision Analysis — Introduction and Fundamental Principles”

Complexity and Complex Systems From Life on Earth to the Universe: A Brief Introduction

1. Overview Dealing with complexity is an integral part of our lives, even if we do not realise it.  An organisation can be modelled as a complex system from the scale of megacorporations right down to the smallest teams. The architecture of software solutions can be equally complicated, and megaprojects and implementations are certainly involved.Continue reading “Complexity and Complex Systems From Life on Earth to the Universe: A Brief Introduction”

Book Review: Programming the Universe — A Quantum Computer Scientist Takes on the Cosmos

Synopsis Most physical theories adopt a mechanistic view when examining natural phenomena where any system can be modelled as a machine whose initial conditions and dynamics govern its future behaviour. In this book, Programming the Universe — A Computer Scientist Takes on the Cosmos, Professor Seth Lloyd proposes a radically different approach centred around aContinue reading “Book Review: Programming the Universe — A Quantum Computer Scientist Takes on the Cosmos”

From Abstract Concepts to Tangible Value: Software Architecture in Modern IT Systems

1. Overview Software design and architecture are two very elusive concepts; even Wikipedia’s entries (ref. architecture, design) are somewhat fuzzy and do not clearly distinguish between the two. The Agile manifesto’s statement on architecture and design is especially brief and raises more questions than answers. The most common definition of software architecture is as follows:Continue reading “From Abstract Concepts to Tangible Value: Software Architecture in Modern IT Systems”

Business Requirements and Stakeholder Management: An Essential Guide to Definition and Application in IT Projects

1. Overview The complexity of business requirements in IT projects has experienced exponential growth due to pressures by increasingly sophisticated client preferences, novel technologies, and fierce competition. Consider, for example, the case of financial payments. In the mid-80s, most payment transactions occurred inside bank branches, and only the biggest banks offered services on ATM orContinue reading “Business Requirements and Stakeholder Management: An Essential Guide to Definition and Application in IT Projects”


Something went wrong. Please refresh the page and/or try again.