Test Driven Development from inside

Isaura Fontcuberta
Lean Mind
Published in
5 min readJul 17, 2018

What follows is a small summary of three great days at the #Leanmind offices in the ITER (Instituto de Tecnología y Energías Renovables) at Tenerife, Canary Islands.

A fantastic team of 16 members with different skills, different coding languages and the same goal: learn how to work better.

TDD da da da… Is all I want to say to you

First Step: Think 🤔

When beginning to solve any problem, you should focus on thinking. Try to understand the problem in your hands. To do that, we create lists to set our method’s behavior before doing ANYTHING ELSE. The focus is to split the problem into smaller pieces to grab every aspect of functionalities our code should solve.

Let’s take a look at these two examples:

Text Wrap:

Our text wrap should split any text in a certain amount of characters we specify in our arguments. The rules are:

- If the text fits the number of characters required, the method should return the text with no changes
- If the word doesn’t fit we should insert a `\n` string (in Unix and all Unix-like systems, `\n` is the code for end-of-line)
- If there is a useful space, we should not split a word but instead insert the end-of-line using the space


// “hello” 5 -> “hello”
// “hello” 2 -> “he\nllo”
// “hello world” 7 -> “hello\nworld”
// “hello” 0 → error
// “” 6 → “”

In the Todo list above, take a look at what the system receives and what it should output. If you take a look at steps 2 and 3, it looks that you might interchange them; however, we realize to succeed at the 2nd step first was a better option. Why?

Because the hack* that we had to perform from the previous step was simpler!

*The hack we talk about is because we try to solve the problem without thinking too much of a complex solution, instead, we should approach the change thinking we are using tricks to make the test pass.

// “hello” 5 -> “hello”
// “hello” 2 -> “he\nllo”
// “hello world” 7 -> “hello\nworld”
// “hello” 0 → error
// “” 6 → “”

Password Validator:

We created a password validator that validates a String. The string should:
- Be at least six digits long
- Contains numbers and letters
- Contains upper and lower case letters

Take a look at the To-do List:

// “Xy3456” = > true 
// “xxxxx” = > false
// “123456” = > false
// “XXXyyy” = > false
// “xy1234” = > false
// “XY1234” = > false

Does something look strange? We begin by validating the good answer first. This order will only work if we return a boolean value because it will lead you to the correct solution first :smile:

The Art of Micro Commits
Now is a good time to create our “First Commit”. What we do along the whole process is to commit once we finish any step. Our git log will look like this:

commit ab17cb65f546a266d439d9805be3b58cd22f9c5b
Author: fontcuberta <isafontcu@gmail.com>
Date: Wed Jul 4 12:28:57 2018 +0100
Green: should reject passwords without lower case letters
commit b37a039674047bb75b9ff8ea4da1279901bece53
Author: fontcuberta <isafontcu@gmail.com>
Date: Wed Jul 4 12:27:58 2018 +0100
Red: should reject passwords without lower case letterscommit 694b80375c6057e0876740106bd0baf833cfd952
Author: fontcuberta <isafontcu@gmail.com>
Date: Wed Jul 4 12:27:07 2018 +0100
Green: should reject passwords without upper case letterscommit e836e9454365c5e89e5b3bb16013272414161d08
Author: fontcuberta <isafontcu@gmail.com>
Date: Wed Jul 4 12:23:13 2018 +0100
Red: should reject passwords without upper case letters

So we commit every time we see a test failing or passing. And also, every time we refactor.

Second Step: Write a test 📋

Now that we have our To-Do list and the behavior of our method properly defined, we create our first test.

When creating a test is essential to describe WHAT the system does instead of the INPUT/OUTPUT it should receive. Take a look at this *Password Validator* example test:

spec.js

it(“should reject passwords without letters”, function () {
expect(passwordValidator(“123456”)).toBe(false);
});

Third Step: See it failing (or the Meta test) 😕

Have you heard that you have to see your test failing before fixing it? Well, you should. The method forces you to run the test right after you wrote it to validate it actually fails. It might sound stupid but this allows you to test the test.

Sometimes developers (myself included) do this in a very mentally automatic way. And this is the real mistake. We know we should run the test to see it fail but we forget to READ (as always) why it is failing.

So let me add what for me is a very important step: read the output of your test!

Your test should fail. And it should fail for only one reason. And you should have only one test failing for only that reason. Now we are fine!

Fourth Step: Hack the code to fix it 😎

Be lazy! Use the simplest way to make the test pass. This will prevent you from adding extra complexity to your problem. You will refactor later.

spec.js


it(“should accept passwords with all the conditions validated”, function () {
expect(passwordValidator(“Xy3456”)).toBe(true);
});

passwordValidator.js

 function passwordValidator () {
return true;
}

Also, try to solve the current problem, not to code three problems ahead even if you know the next steps.

Fifth Step: Refactor ♻️

I mean to post another whole post about refactoring because this post was getting too long. However, these tips might help you when refactoring:

  • Underscores improve readability so when possible use snake-case instead of camel-case
  • When pair programming, the driver should focus on the current situation and the navigator should write stuff for the future
  • Try to keep the same abstraction level along the code
  • Code for today. Don’t try to write code for the future
  • If a detail should be visible, I could add an explaining variable
let itFits = text.length < maxLineWidth;
if itFits {
return text;
}

Otherwise, I might extract it in a different function

if itFits(text) {
return text;
}
function itFits(text) {
return text.length < maxLineWidth;
}
  • Change the name to improve abstraction before looping to detect recursive opportunities
  • We don’t fall away with architecture; we do emergent design to create methods to fulfill this architecture

End: Do it all again 🏁

Yes. This is an iterative process. When you are done you are ready to begin. The process itself will tell you when to stop and when your code unit is ready to go!

Ready? Let’s TDD

--

--

Isaura Fontcuberta
Lean Mind

Product Designer / Product Owner / Software Engineer / Learning Helper