TDD by example – Factorial

Test-driven development is a development style with a simple process:

1. Write a failing test
2. Write code that corrects the failing test
3. Clean up your code
> Go to 1.

The goal of TDD is not writing the tests first, it’s a design process where you iterate over the design as you develop new code. Instead of doing a full upfront design, you design little by little as you need more functionality.Writing test first is just a tool to force you to focus on a small part of the code, how that part should work and improve the design all the time.

Generally doing TDD at-least guarantees tests and testability. It does not provide efficiency or quality in itself (I have not found any documented evidence of this). But it does provide some minimum level of quality and encourages the developers to think about the problem in a modualized way. It also makes sure that developers write tests. We all now that writing tests after committing the code is hard because of all those excuses: late friday night, pressure, the sprint is ending and we just need some other functionality done, etc.

Another big benefit from TDD is that it also (help) eliminates the waste from created from developers implementing stuff that might be useful. No code can be written before a test-case requiring that functionality.

You can find a full description of TDD on wikipedia.

Benefits of TDD:

TDD will make sure your code are testable and well tested. The high test-coverage will form an excellent documentation of your code.

  • automatically gives you testable code, by definition
  • ensures high test coverage
  • elimintaes waste from implementing stuff that might be useful som time.
  • shorter feedback loop
  • higher code confidentiality
  • makes you focus on smaller parts of the problem
  • forces you to think about the API’s before implementing code.
  • helps with modularizing of the code
  • will make it easier to refactor your code, because of the high test coverage
  • makes you iterate and improve the design throughout the whole development process
  • high test-coverage provides excellent documentation of your code

Shortcomings of TDD

A higher number of tests can not guarantee higher code quality, it can only provide you with a minimum level of quality of the resulting product.
  • can be time-consuming, especially in the beginning
  • can be hard, especially dealing with frameworks which put constraints on your code
  • done wrong: can make it hard to change the code. This is because there is so many tests everywhere that verify every little part of your code all the time
  • can be hard to prove that it actually are more cost effective, especially in the beginning
  • Can make you less productive, especially if you follow a very strict TDD-model where you only do the smallest change possible to satisfy a failing test. I often feel that I would be able to solve larger part of problem at once when i do TDD.

TDD in action

Now, after providing some background, lets start doing TDD, iteration for iteration. I will show you all the steps required.

Problem description

The task is to implement an factorial method in Java. The definition of factorial is:

Examples:

0! = 1
1! = 1
2! = 2 x 1 = 2
3! = 3 x 2 x 1 = 6

Limitaions: We will limit our self to only use the primitive int type in Java. This simplifies our problem, but limits the resulting number to 32bit. this means we will only do up to 10!.

Iteration 1

Lets start simple 0! should be 1.

Write test

We write our first test-method:

    @Test
    public void shouldReturnOneWhenZeroIn(){
        assertEquals(1, factorial(0));
    }

As we write this test we will get an compilation error complaining about the missing factorial method. I auto-generate this method in my IDE to become:

     private int factorial(int i) {
        return 0;
    }

I execute the test and bam:

junit.framework.AssertionFailedError:
Expected :1
Actual :0

Fix the failing test

Lets just fix it:

     private int factorial(int i) {
        return 1;
    }

Hurray our test is now running green!

Clean up code

I don’t see any reason to clean up the code yet. It’s simple and it solves the test case.

Iteration 2

Ok lets expand 1! should also be 1.

Write test

We write our first test-method:

    @Test
    public void shouldReturnOneWhenOneIn(){
        assertEquals(1, factorial(1));
    }

Wot, the test went green? I guess our previous implementation already covers this case, lets just head to next test case.

Iteration 3

Ok, in this iteration we want to make sure that 2! should be 2.

Write test

    @Test
    public void shouldReturnTwo() {
        assertEquals(2, factorial(2));
    }

Fix the failing test

    private int factorial(int i) {
        if(i < 2) return 1; 
        else return 2;
    }

Hurray, our tests are now green again.

Clean up code

I clean up the code by adding curly-braces.

    private int factorial(int i) {
        if(i < 2) {
           return 1;
        } else {
            return 2;
        }
    }

Iteration 4

In this iteration we want to make sure that 3! equals 6.

Write test

    @Test
    public void shouldReturnSix() {
        assertEquals(6, factorial(3));
    }

Executes and verifies that the test is failing.

Fix the failing test

      private int factorial(int i) {
        if (i < 2) {
            return 1;
        }

        if(i == 2) {
            return 1*i;
        } else {
            return 1*2*i;
        }
    }

Hmm.. do we start to see a pattern?

Clean up code

No clean up’s this time, I am happy with the code as it is.

Iteration 5

Write test

@Test
public void shouldReturnTwentyFour() {
  assertEquals(24, factorial(4));
}

Fix the failing test

We started to see a pattern in last iteration. Let’s try to do a recursive function where we multiply i with factorial(n-1).

private int factorial(int i) {
  if (i < 2) {
    return 1;
  }
    return factorial(i-1)*i;
  }

Hurray, it worked.

Iteration 5

From my manual calculator I find that 10! = 3628800. Lets do a test for it

Write test

    @Test
    public void shouldFindCorrectFactorialFor10() {
        assertEquals(3628800, factorial(10));
    }

Yes, my code return’s correctly.

Clean up test-code

Our test-cases are also duplicated code. We can clean this duplication by using an multidimensional array to represent each input/output values.

    @Test
    public void shouldReturnCorrectFactorialValue() {
        int values[][] = {{0,1}, {1,1}, {2,2}, {3,6}, {4,24}, {10, 3628800}};
        for(int[] value: values) {
            assertEquals(value[1], factorial(value[0]));
        }
    }

For me this feels natural. I do this to avoid having multiple of testMethods for every input value we are testing.

Summary

As you saw, we quickly found the recursive solution of this problem. Because the faculty operation is a well known problem, I would probably head straight to a similar solution without a test-first approach.

The point of this example is just to show the process and how it is performed. The benefits is generally more “visible” when the task faced is larger and more complex, where it is harder to see all the challenges required to solve upfront.

TDD gives us tested code, with shorter feedback loop, higher code confidentiality and hope of code quality and improved design. At least the developers have been forced to implement with speration of concerns in mind. More tests does not provide quality in itself and it all comes back to highly skilled developers.

Advertisements

7 thoughts on “TDD by example – Factorial

  1. Pingback: Why writing tests are worth it « Ivar Conradi Østhus

  2. Thank you.

    two questions ,

    1) how to decide to introduce a new class when doing TDD?
    2) how to decide whether to use a Mock or jump start with a new class ?

    • That’s great questions!

      1) There is no easy answer here, it always depends. I usually prefer to write the code needed first. Then go back, split it in to smaller methods, and after a while try to see if something really is another concern and should be refactored out in to another class. It also helps using small diagrams and have single responsibility principle in mind.

      2) Usually this depends on what code you are writing. I tend to use Mock’s if i write some code that will require to interact with some external resource (database, service, etc.) and the interaction is not the interesting part.

  3. I was excited to discover this great site. I need to to thank you for your time for this fantastic read!! I definitely liked every part of it and i also have you book-marked to look at new stuff in your site.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s