Learn about ▪ 3 Laws of TDD by Uncle Bob ▪ 2 rules of TDD by Kent Beck ▪ 3 steps of TDD by Martin Fowler ▪ 4 common misunderstandings about these theories.

In this article you will learn about:

When it comes to disagreements, we will discuss the below issues:

Introduction to TDD articles’ series

Welcome to the series of blog posts written by the team of Web Developers at Droids on Roids. While our group meetings we found out that we have different perceptions on various aspects of Test-driven Development (TDD). We decided to publish the conclusions from our long debates in the form of discussing and clarifying 6 Misconceptions about TDD.

TDD definition

Test Driven Development (TDD) is a process of creating software which uses automated tests not only as a tool for proving the code correctness but it leads to developing applications in a clean and well-designed way.

The TDD basic idea relies on working in a cycle (known as TDD-cycle) consisting of 3 stages:

Creating a test (RED – test fails)

(RED – test fails) Adding a code required to pass the test (GREEN – test passes)

(GREEN – test passes) Refactoring (BLUE)

All experienced developers know the basics, but when it comes to details, many misunderstandings can arise. To make the TDD definition clear, you need to understand it in depth.

Where to find TDD definition?

To refresh the canonical definition of TDD, you should go back to the basics and review the materials which we can easily find surfing the Internet.

The most straightforward source of knowledge is Wikipedia. It is a mix of all universal knowledge, so it is the right place to start. We can learn there how TDD cycle looks like, what are the benefits of it and a lot more.

However, if we want to understand the process deeply, we should jump to its origins. According to the Wikipedia article, the person credited for the development of TDD is Kent Beck. It seems that his book “Test Driven Development: By Example” is the main and primary source of knowledge about Test-driven development.

Two rules of TDD by Kent Beck

The author of “Test Driven Development: By Example” guides readers through sample problems solving them using the TDD technique.

It might be the best way to learn the process however we would like to have some clear rules which we would call TDD definition. At the very beginning of the book, Kent mentions that the roots of TDD can be described as:

Don’t write a line of new code unless you first have a failing automated test Eliminate duplication

The first rule seems to be quite obvious. It just says that we shouldn’t start writing application code until we have a single failing automated test. When the test is Green, and we want to continue working on the production code, we should add a new test. Actually, we should do Refactor first but it is the second rule.

The second rule, “Eliminate duplication”, looks more confusing. In general, it is the Refactoring phase, which means that we should keep our code clean using standard Refactoring methods like extract method.

However, in details, this rule was the source of many misunderstandings in our dev team. It is essential to understand it deeply.

As we found out that “Eliminate duplication” is something more than typical Refactoring. It is more about decoupling the tests and production code by removing duplication between them.

The production code and checks should be as much independent of each other as possible. The reason for doing so is to make it possible to write the next test with minimal effort. If you feel that it is not entirely clear to you, you should look to the book and analyze how Kent Beck is doing it.

Two simple rules imply a sophisticated process

Those two mentioned rules are the roots of TDD. They are simple, but as Kent describes the implication of them is a sophisticated rhythm of code development.

They lead to Red, Green, Refactor cycle known as TDD-cycle.

They allow us to focus on a small scope of our problem.

On each new test, we decide how big scope of our problem the test should cover. It should depend on our confidence. If we know exactly what to do, we can try to cover bigger scope but if we fell that we don’t fully understand the domain of the problem we can start from a smaller one.

Kent Beck mentions many times that one of the most significant benefits of TDD is that we are allowed to work in really small steps. By “step” we mean the size of the code which we need to add to complete one Red, Green, Refactor cycle round.

Here comes the next misunderstanding which we have faced. How big should be such a step?

Some people claim that smaller steps are better, others prefer to do a bigger one, but in fact, both ways seem to be correct. What to choose? It depends on your experience and understanding of the problem you are working on.

Thanks to TDD we can start with a big step that covers a significant part of the problem (even application level test), but in case of any difficulties, we can easily switch to smaller steps.

The important thing is that thanks to TDD you can work in tiny steps. The step’s size should be flexible and should depend on your preferences.

Thanks to all of these benefits, TDD gives us an easy and smooth way to create better design and the excellent interface of our module/class. Of course, it is not guaranteed by TDD (everything can be done wrong), but for sure it helps to achieve this goal.

The Three Laws of TDD by Uncle Bob

After the lecture of Kent’s book, we should have good knowledge of TDD. We can get good intuition going through examples from the book, but still, we can have doubts.

Looking for a more detailed description, we found Uncle Bob’s “The Three Laws of TDD ” article. It introduces three short but stringent rules on how to do TDD right:

You are not allowed to write any production code unless it is to make a failing unit test pass You are not allowed to write any more of a unit test that is sufficient to fail, and compilation failures are failures You are not allowed to write any more production code that is sufficient to pass the one failing the unit test

This looks like a good, almost formal description. In every moment of coding, we know precisely in which point we are and what we should do.

The problem is the Refactoring phase. In such a stage, we should be allowed to clean up the code, and rules by Uncle Bob don’t let us do so.

However, if we would use Uncle Bob’s rules only for Red and Green steps of TDD-cycle, we will stay consistent with Kent examples of TDD.

So, it makes sense to comply with Uncle Bob’s rules starting from the Red phase, until Green one. For Refactoring, we make a break from Uncle Bob’s rules.

What does Martin Fowler say?

The last source about TDD which we want to mention is a short but useful article – “Test Driven Development” written by Martin Fowler. He describes TDD as three steps:

Write a test for the next bit of functionality you want to add Write the functional code until the test passes Refactor both new and old code to make it well structured

The rules cover the same issues that Kent Beck describes. Martin underlines the importance of Refactoring. To be precise, he mentions that during the Refactoring phase, we can refactor all code not only the last added lines.

He suggests that the biggest mistake which programmers do in TDD is using only the first two rules. Some of our developers claim that omitting Refactoring corresponds to Test First technique known from Extreme Programming.

Misunderstandings about TDD rules

Reviewing all the mentioned materials should be enough to get a good understanding of what TDD is. It should help us to analyze the problems which we faced here in our team. Some of them has already been mentioned but there was a few more which we would like to discuss.

Disagreement 1: Can we mix Green and Refactoring phase of TDD-cycle

The misunderstanding here is related to Refactoring phase of Red, Green, Refactor cycle. Can we mix the Green and Refactoring phase?

The reviewed materials seem not to leave doubts here that Coding and Refactoring should not be combined.

According to Kent Beck’s book and “Three Laws of TDD,” it is clear that we should at first focus to make failing test pass with the smallest possible efforts. Having it Green, having all tests Green, we can safely refactor the code to clean it up and improve the design.

As Martin Fowler wrote, neglecting Refactoring phase is the most common cause of bad design and screws up all TDD process.

For many people, such an approach with no Refactoring stage fits more to a technique called TFD (Test first development) based on Extreme Programming Test First pattern.

Disagreement 2: Do we really need to refactor the code immediately after the Green phase?

In the previous disagreement, we made it clear that the Refactoring phase is necessary. The next question is, do we need to refactor the code immediately after switching from Red to Green phase?



The problem here is the understanding of Refactoring. We can think about it only in the context of production code like extract method.

However, as Kent Beck shows, Refactoring is something more. He describes it as “Eliminating duplication”. During the Refactoring phase, we can change dump implementation into a real one just by removing duplication between code and tests.

If we want to be consistent with Kent Beck, we should do it after every test. Of course, if there are any duplications to be removed.

We have faced a problem of omitting Refactoring in our team doing “coding dojo” exercise. Its idea is to solve a problem as a team where every person codes for 5 minutes and switches with another team member.

We decided to work in TDD style, but because of lack of time, we haven’t done Refactoring after every test. We end up in the place where the interface of our code was so coupled with the tests that any change of code caused all tests to fail.

The only rescue was to do colossal Refactoring. Probably we would not have this problem if we would do Refactoring more frequently.

Can sometimes Refactoring be unnecessary? It can be useful to do a few more Red, Green phases before cleaning up a just added code.

It happens often, that when we are writing a test, we already know how the next test should look like. We don’t want to forget it so we write the next test before making the previous one Green. This is actually wrong – I write about it in the next Disagreement: Can we write more than one failing test at once? Why / why not?

But sometimes the added code to make the test Green is good enough. For example, let’s consider such code:

Tests describe NaturalNumber do it “decreases number” do number = NaturalNumber.new(5) number.decrease expect(number.to_i).to be eql(4) end end 1 2 3 4 5 6 7 describe NaturalNumber do it “ decreases number ” do number = NaturalNumber . new ( 5 ) number . decrease expect ( number . to_i ) . to be eql ( 4 ) end end

Code class Money def initialize(value) @value = Integer(value) end def to_i @value end def decrease @value -= 1 end end 1 2 3 4 5 6 7 8 9 10 11 12 13 class Money def initialize ( value ) @ value = Integer ( value ) end def to_i @ value end def decrease @ value -= 1 end end

Let’s say that the requirement is:

Decreasing NaturalNumber below 0 should be forbidden.

We add the test and the code making it passes like below:

Tests describe NaturalNumber do it “decreases number” do number = NaturalNumber.new(5) number.decrease expect(number.to_i).to be eql(4) end it “raises error decreasing 0” do number = NaturalNumber.new(0) expect do number.decrease to raise_exception end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 describe NaturalNumber do it “ decreases number ” do number = NaturalNumber . new ( 5 ) number . decrease expect ( number . to_i ) . to be eql ( 4 ) end it “ raises error decreasing 0 ” do number = NaturalNumber . new ( 0 ) expect do number . decrease to raise_exception end end

Code class Money def initialize(value) @value = Integer(value) end def to_i @value end def decrease raise RuntimeError unless @value > 0 @value -= 1 end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Money def initialize ( value ) @ value = Integer ( value ) end def to_i @ value end def decrease raise RuntimeError unless @ value > 0 @ value -= 1 end end

We added the test, added the code, and we can decide that Refactoring is not needed here. Even if we would see that there is something to be refactored, it doesn’t seem to be a big sin.

However, if there would be duplications between code and tests like Kent Beck describes in his book, we should refactor it as soon as possible. It is possible that it would block us from adding new test without making the previous one fails.

Disagreement 3: Can we write more than one failing test at once?

All the reviewed resources point that we should add or edit only one test per time. The “Three Laws Of TDD” are very strict here saying that we should not write more test code that is needed to fail the test.

The reason of fail can be just failing assertion. But it can be an exception which we get from interpreter or compiler that the class, module or function which we test, is not defined.

What is the reason why to do write only a single test?

TDD is not only about making sure that the code is correct but also about building clean code with well-defined API (interface of its modules/classes).

Thanks to writing only one test per time, we can focus on a small piece of code needed to pass the test. It is easier to extend the existing interface to cover the new requirement.

If it would be allowed to add many tests omitting this rule, someone would probably try to design all module/class at once covering it with all possible test cases. That could lead to a bad interface.



We can sense that it is better to build tested object in few iterations than inventing its whole interface at once. For sure, being strict with this rule helps.

The question is what to do if we know what the next tests should look like and we don’t want to forget adding them?

This rule bounds us here what can make us feel annoyed. Kent Beck suggests using “todo list”. We can write down all the test cases (at least descriptions of them) and add them later. It can be even a paper list, a comment in the tests file or anything which we want. Some tests frameworks allow us to write pending tests like Ruby RSpec what we can use as a todo list.

Pending tests describe Money do it “returns its value” do expect(Money.new(1).value).to eql(1) end it “returns currency” it “is immutable” end 1 2 3 4 5 6 7 8 describe Money do it “ returns its value ” do expect ( Money . new ( 1 ) . value ) . to eql ( 1 ) end it “ returns currency ” it “ is immutable ” end

Another problem which we found related to this disagreement is because of frameworks which we use for tests. For example, let’s consider an example of tests in Ruby RSpec:

One test describe Money do subject { Money.dolar(5) } it { expect(subject.to_a).to be eql([5, “USD”]) } end 1 2 3 4 5 describe Money do subject { Money . dolar ( 5 ) } it { expect ( subject . to_a ) . to be eql ( [ 5 , “ USD ” ] ) } end

Two tests describe Money do subject { Money.dolar(5) } it { expect(subject.to_a.first).to be eql(5) } it { expect(subject.to_a.last).to be eql(“USD”) } end 1 2 3 4 5 6 describe Money do subject { Money . dolar ( 5 ) } it { expect ( subject . to_a . first ) . to be eql ( 5 ) } it { expect ( subject . to_a . last ) . to be eql ( “ USD ” ) } end

We wanted to test the factory method, which should create an object representing 5$. It should be convertible to pair (array) where the first element is value property, and the second is a currency.

In the example “One test”, we used only one test for it.

In the second example “Two tests”, we did the same in two tests, violating one failing test per time rule.

Many Ruby developers would probably prefer the “Two tests” option because, in the case of fail, we get more precise information in console.

Of course, we can argue that the first example is just the same as the second one but done in a bigger step. If we prefer “Two tests” option and want to be faithful to TDD, we should write a single test from the “Two tests” example, do Red, Green, Refactor cycle and then write the second test. However, most of us would agree that it should not be a big sin if we write both at once in such a case 😉

Disagreement 4: How small steps should we take?

The step in the sense of testing is the size of the production code, which we need to add to make the test pass.

The disagreement which we had here is how big those steps should be. We can write application level tests which can be considered as huge steps or test every single method one by one.



As Kent Beck writes, it should depend on us how big step do we want to take. The critical thing about TDD is that at any time, we can switch to smaller steps if something gets wrong.

The TDD community tends to do smaller than bigger steps, but probably it should depend on your personal experience and knowledge of domain you are working on.

Why there are so many misunderstandings?

Every developer at least has heard about TDD, and more experienced ones probably are using it every day.

The problem can be how we learn about software development and in particular about TDD. We can learn about it from a lot of different sources like books, blogs, during studies, or from our colleagues.

The sources of knowledge are common, but during our daily basis, we work on different projects in different technology stacks. Getting more experience over time, we build our vision about software development and in particular about TDD. Our perception of these issues is naturally different like the color of this dress – it is different depending on who looks at it 😉



Our understanding of TDD can vary a lot, depending are we frontend, backend, static type, dynamic type, object, or functional developers.

Sometimes we think that we have a perfect intuition what TDD is, but if we don’t understand it in depth, we can have a false vision of it.

Summary

How can we define TDD then? If someone would ask me now, the best what comes to my mind is:

TDD is the process of software development focused on creating well-designed code. It requires to write the code in Red, Green, Refactor cycle known as TDD-cycle. Before touching the production code, we do a single automated test. Next, we add the simplest possible code which makes it pass. We can choose the size of the step which we want to take here. It can be really granular single method test or huge acceptance test. If something goes wrong, we can go back and choose a smaller step or even return to previous Refactoring phase. If the new test is Green, we perform Refactoring when we eliminate duplications between the code and test, improve the quality and clean the design. Thanks to that, we should be able to easily add the next test and end with a clean code with a well-defined interface.

I have feeling that the above definition is still not good enough and leaves a lot of place for misunderstandings. TDD is the approach to the development process, and it seems to be hard to find a formal definition for it.

The “The Three Laws of TDD” of Uncle Bob is something which is the closest to be precise, short definition of TDD. However, to deeply understand how to do TDD, you should study “Test Driven Development: By Example” by Kent Beck.

As we realized every one of us has a little bit different look on TDD. It seems that it is not possible to understand it with a single definition. We all need to practice TDD in our projects to get a better understanding of it.