Test Driven Development (TDD) Tutorial

Introduction:

Kent Beck, one of the original signers of the Agile manifesto and creator of eXtreme Programming, is credited with discovering Test Driven Development (TDD), but he claims he merely re-popularized the technique and coined the term. Here is what he had to say about rediscovering the technique:

“The original description of TDD was in an ancient book about programming. It said you take the input tape, manually type in the output tape you expect, then program until the actual output tape matches the expected output.
After I’d written the first xUnit framework in Smalltalk I remembered reading this and tried it out. That was the origin of TDD for me. When describing TDD to older programmers, I often hear, “Of course. How else could you program?” Therefore I refer to my role as rediscovering TDD.”

TDD is a highly disciplined approach to developing software. The technique has been around much longer that you may guess. It was not called TDD at the time, but the technique was used way back in the 1950’s by NASA during project Mercury.

 

Tutorial:

Lets use TDD to develop an simple ‘Uasi’ modem class. Uasi is a silly childhood language akin to Pig Latin. Here is how it works:

Exchange every vowel with the next vowel in alphabetical sequence. ‘U’ wraps back around and is replaced by ‘A’.

“My name is Tomas” -> “My nemi os Tumes”

Unit tests exist to prove that the desirable behaviors remain in place throughout future development and refactoring. One test per behavior. How many behaviors/tests do you think an Uasi modem should have? One? A few? Many? Let me rephrase the question: How many regression bugs are possible in an Uasi modem?

I can think of at least 20 desirable behaviors that must be in place for a working Uasi modem, and that’s just to cover the happy paths

  1.  All ‘a’ characters of the encoder input are replaced with ‘e’ characters in the encoded value.
  2.  All ‘e’ characters in the decoder input are replaced with ‘a’ characters in the decoded value.
  3.  All ‘A’ characters of the encoder input are replaced with ‘E’ characters in the encoded value.
  4.  All ‘E’ characters in the decoder input are replaced with ‘A’ characters in the decoded value.

… you get the idea

There are additional expected failure behaviors to consider as well. What is the desired behavior when input is null? Do we expect some kind of InvalidInputException or to return emptyString?

To develop our Uasi modem in a test driven fashion, we will begin the TDD rhythm of “Red, Green, Refactor”.

Red – Write 1 new unit test that covers a single desired behavior. This test should fail because the new desired behavior doesn’t exist yet. This temporary failing test will show up as red in the jUnit report results. That’s how this step gets its name.

Green – Write just enough code to make the test for the new behavior pass. Be mindful not to add any additional behavior yet that is not covered by any tests yet. Your newest test should turn ‘green’ once the new desired behavior is in place.

Refactor – Refactor your testSubject to clean up the implementation of the existing behaviors with confidence that all desired behaviors remain in place because all unit tests continue to pass. Be mindful not to introduce new behaviors that are not covered by tests during refactoring.

Lets start the RGR rhythm with our first test:

 

At this point the test will fail. Actually it won’t even compile yet. The testSubject class doesn’t exist yet, and neither does its .encode() method.  Lets finish the Red step by making this test compile.

 

Now our first test compiles, but fails when executed. Lets continue to the Green step and make this test pass by implementing the desired behavior with the bare minimum amount of code.

The unit test of our first behavior now passes, so we can move on the Refactor step where we do cleanup and make the code fit our coding standards.

 

That concludes our first RGR cycle. Continuing the Red, Green, Refactor rhythm will carefully and robustly grow our Uasi modem’s desired behaviors with confidence that regressions are not happening along the way. At the end of our second RGR iteration, our code may look something like this:

 

After many RGR iterations, here is the tests I came up with, along with 3 entirely different implementations. I doubt any of these implementations are anything like what you had in mind, but they all work perfectly fine and the tests prove it.

Tests:

Jr Level implementation:

Sr. Lever Implementation:

Master Craftsman Implementation:

Who knows how/why this works, but its fewer lines of code so it must be better 🙂

 

Now that the tests and a passing implementation are in place, we can refactor with confidence that the desirable behavior is being preserved. Try your own implementation and see if your implementation passes all the tests first try. Get these files from my Github: https://github.com/bkturley/uasiModem.

Try it yourself

As a self directed exercise, try to TDD an “Pig Greek” service. Pig Greek, also known as ‘Obish’, is another silly language sort of like Pig Latin, but easier than Pig Latin to implement because it is encoded by vowel sound position rather than by syllable position. Programmatically breaking English words into syllables is just too complicated and distracts from the purpose of the exercise. Here’s how to speak Pig Greek:

Simply add “Ob” before every vowel sound.

“My horse is in the barn” -> “MOby hOborse Obis Obin thObe bObarn”

This may seems pretty straightforward, but we are dealing with the English language, so there are certainly nuances that make the process non trivial and interesting. The word “my” for example contains no vowels, but does have a vowel sound. The word “horse” has a silent ‘e’ that makes no vowel sound. The word “teeth” has two consecutive vowels, but only one long ‘e’ vowel sound. Correctly decoding Pig Greek has many opportunities for bugs when ‘Ob’ is supposed to be preserved (“microbe”, “obey”, “global”).

 

Fun fact: Special languages like Uasi and Pig Greek that are used for secret communications while among larger groups have a named classification. They are collectively known as ‘Argot’ (pronounced are-go), ‘Cant’, or ‘Cryptolect’.

Answering a Recent Interview Question…Thoroughly

Here is a coding exercise I was asked to solve during a recent interview:

Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …
By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.

This is a basic solution, it is what I consider a rough prototype.

Here is a more architected solution. Tasks have been separated into classes with clear and narrowly defined responsibilities. I consider this a decent draft 2. This code is still not ready to be used in a production system.

 

A complete Solution:

 https://github.com/bkturley/fibonacciUnitTested

Fleshed out unit tests make this project ready for use in a production system.