Home
The Spec
The Language
The Practice
The Books
Links





A Simple TDD Example

Let's make a calculator object that allows you to operate on two numbers using Test-Driven Development (TDD). The book Test-Driven Development says that in order to do this we must follow two simple rules:
  1. Write new code only if an automated test has failed
  2. Eliminate duplication
Here are some reasons why using TDD is valuable:

 

  • You don't have to worry about fixing bugs when you are finished programming.
  • You can modify the design right when you see a better way to do it.
  • You can work with others now because you know that they aren't destroying your code.
  • You always move forward because work already accomplished doesn't come undone.

The best way to read this article is to type in the examples and run the tests yourself. To do this, you should first read about how to set up your Test-Driven JavaScript for ASP .NET environment. Then you will have what you need to make it work.

We are going to start by first testing for the calculator code as if it were already there. This requires that we think about what the calculator object is going to be like before we start typing. The book recommends that we start out with an "assertion". This is simply a line of the program that makes a logical assumption. If it is correct, then the test passes. If it isn't the test fails. In this case we will test to see if putting two numbers into our calculator and requesting that it "add" will actually result in the two numbers added together.

Here is our first JSNUnit test. This will need to be typed into the file called "ServerTests.js":

 

 

newTest("Test the adding of two numbers").Execute = function () {
  this.AreEqual(2, calc.add(1,1), "One plus one should equal two");
}

 

This piece of code creates a new Test object and then immediately overrides the "Execute" function. There is only one line in thie "Execute" function and it checks to see if "calc.add(1,1)" is equal to 2. It provides a note: "One plus one should equal two" that will display if the test fails. Notice that we haven't written anything but a test. Now, let's fire up the "TestRunner27.aspx" and see what happens:

First Compilation Error

We didn't really expect this to work yet because we haven't made the object yet. We are following rule number 1: "Write new code only if an automated test has failed". Well, we have failure now. That was easy. The error seems to be telling that we don't have a variable named "calc". Ok, let's make one:

 

newTest("Test the adding of two numbers").Execute = function () {
  var calc;
  this.AreEqual(2, calc.add(1,1), "One plus one should equal two");
}

 

Now let's refresh our runner and see what it says now:

Second Calculator Error

Well, now there's a variable called "calc" but it doesn't contain an object. Let's put an object in it then:

 

newTest("Test the adding of two numbers").Execute = function () {
  var calc = {};
  this.AreEqual(2, calc.add(1,1), "One plus one should equal two");
}

 

And try the runner again:

Calculator Test 3

The compiler says it expects a function on line 19. That's because we tried to do the function "add()" on an empty object in the variable "calc". We need "calc" to have an "add()" function or method. It is good to notice that we are making slow but steady steps forward. This is very typical of TDD. You may not always move fast, but almost always move forward. Sometimes, when the software you are working with is mature, you go fast as well. Let's give the object in "calc" an "add()" method:

 

newTest("Test the adding of two numbers").Execute = function () {
  var calc = {};
  calc.add = function () {
  }
  this.AreEqual(2, calc.add(1,1), "One plus one should equal two");
}

 

Let's try the runner again:

Calculator Test Run 4

We passed the compiler and are now being stopped by the test that we wrote. Not only does 1 + 1 not equal 2, it equals "undefined". That's most likely because we aren't returning anything at all from the "add()" method we just made. Let's return the correct value:

 

newTest("Test the adding of two numbers").Execute = function () {
  var calc = {};
  calc.add = function () {
    return 2;
  }
  this.AreEqual(2, calc.add(1,1), "One plus one should equal two");
}

 

And try again:

Our tests passed this time. They did because we returned exactly what the test asked for. Actually, the program works perfectly. Every time you add 1 and 1 you get 2. Before you judge the code too harshly, remember that the test does not specify anything else but to make sure that when 1 and 1 are sent, 2 comes back. In order to make sure that something other than two comes back, we must specify that it should come back differently in different situations. Let's make one of those situations now by adding this new test:

 

newTest("Test the adding of two numbers").Execute = function () {
  var calc = {};
  calc.add = function () {
    return 2;
  }
  this.AreEqual(2, calc.add(1, 1), "One plus one should equal two");
  this.AreEqual(4, calc.add(2, 2), "Two plus two should equal four");
}

 

Now what happens?

Calculator Test Run 6

Now it is obvious that our add feature only returns two. But have we really specified how we want it to work yet? Not necessarily. What if I do this to fix it:

 

newTest("Test the adding of two numbers").Execute = function () {
  var calc = {};
  calc.add = function (aNumber) {
    return aNumber * 2;
  }
  this.AreEqual(2, calc.add(1, 1), "One plus one should equal two");
  this.AreEqual(4, calc.add(2, 2), "Two plus two should equal four");
}

 

Calculator Test Run 7

It works, and we only used the first number to calculate the answer. We can tell by looking at this that it won't work as an add function should yet. I think we need to really give it something different, like this:

 

newTest("Test the adding of two numbers").Execute = function () {
  var calc = {};
  calc.add = function (aNumber) {
    return aNumber * 2;
  }
  this.AreEqual(2, calc.add(1, 1), "One plus one should equal two");
  this.AreEqual(4, calc.add(2, 2), "Two plus two should equal four");
  this.AreEqual(12, calc.add(7, 5), "Seven plus five should equal 12");
}

 

Refresh again,

Calculator Test Run 8

And the problem with the add function is exposed. Now it is very difficult to fix the problem without really making the addition work. It's pretty obvious what is intended now:

 

newTest("Test the adding of two numbers").Execute = function () {
  var calc = {};
  calc.add = function (firstNumber, secondNumber) {
    return firstNumber + secondNumber;
  }
  this.AreEqual(2, calc.add(1, 1), "One plus one should equal two");
  this.AreEqual(4, calc.add(2, 2), "Two plus two should equal four");
  this.AreEqual(12, calc.add(7, 5), "Seven plus five should equal 12");
}

 

And now it works for all cases:

 

Test Results (JSNUnit 2.7 Server-side):

    Pass

    Test count: 3, Check count: 6

Refresh browser to run test again.

 

The code we've made is a bit unusual because it is together with the tests. Usually the tests are separated out into their own .js file so that when it comes time to use the software you can use just the code without the tests included. We will be doing that later, but in preparation for this code to be on it's own, we need to change it a little. This code makes an object. Code that makes an object is called a constructor. Let's put this code into a constructor function. This function will put the object together and give it to us as a result. Again, instead of just making the code, lets test the new function that we want to make by making these changes:

 

newTest("Test the adding of two numbers").Execute = function () {
   var calc = NewCalculator();
   calc.add = function (firstNumber, secondNumber) {
      return firstNumber + secondNumber;
   }
   this.AreEqual(2, calc.add(1, 1), "One plus one should equal two");
   this.AreEqual(4, calc.add(2, 2), "Two plus two should equal four");
   this.AreEqual(12, calc.add(7, 5), "Seven plus five should equal 12");
}

 

This results an error:

 

Compiler Error Message: JS1135: Variable 'NewCalculator' has not been declared

Source Error:

Line 16:     
Line 17:     newTest("Test the adding of two numbers").Execute = function () {
Line 18:       var calc = NewCalculator();
Line 19:       calc.add = function (firstNumber, secondNumber) {
Line 20:         return firstNumber + secondNumber;

Source File: C:\WebSites\Calculator\ServerTests.js    Line: 18

 


The function NewCalculator hasn't been made yet so we get an error. JSNUnit provides a separate file for code and a separate file for tests by default. The file for server-side code is called "ServerCode.js". Let's move our new code to this file now. Open ServerCode.js from Solution Explorer by double-clicking on it and take this code out of ServerTests.js and put it in the ServerCode.js file.:

 

function NewCalculator () {
}

Now when we run the test runner we get this:

Exception Details: Microsoft.JScript.JScriptException: Object required

Source Error:

Line 17:     newTest("Test the adding of two numbers").Execute = function () {
Line 18:       var calc = NewCalculator();
Line 19:       calc.add = function (firstNumber, secondNumber) {
Line 20:         return firstNumber + secondNumber;
Line 21:       }

Source File: C:\WebSites\Calculator\ServerTests.js    Line: 19


Our NewCalculator function doesn't return a calculator, in fact, it doesn't return anything yet. So, we get an error. Now it's time to move our code out of our test file and into the ServerCode.js. After we do this the two files should look like this:
ServerCode.js:

function NewCalculator () {
    var calc = {};
    calc.add = function (firstNumber, secondNumber) {
       return firstNumber + secondNumber;
    }
    return calc;
}

ServerTests.js:

newTest("Test the adding of two numbers").Execute = function () {
   var calc = NewCalculator();
   this.AreEqual(2, calc.add(1, 1), "One plus one should equal two");
   this.AreEqual(4, calc.add(2, 2), "Two plus two should equal four");
   this.AreEqual(12, calc.add(7, 5), "Seven plus five should equal 12");
}

Then our tests pass:

Test Results (JSNUnit 2.7 Server-side):

    Pass

    Test count: 3, Check count: 6

Refresh browser to run test again.

Now we have a tested calculator function written in JavaScript by writing the tests first.

If you want to try Test-Driven development with JavaScript right now you can do so in your browser using the Test-Driven JavaScript Writer. If you want to read more about the concept, I recommend this book by Kent Beck.


Troy Taft is the Principal Consultant and founder of Troy Taft Consulting, a firm specializing in high value software development. He also authors a free monthly newsletter called Software Matters.

Copyright 2007 Troy Taft All rights reserved, you may print this article for your personal use.