CSC/ECE 517 Fall 2012/ch2a 2w11 aa

From PG_Wiki
Jump to: navigation, search

Contents

Introduction

Test driven development (TDD) is a process that tries to create the minimal amount of code while meeting customer's expectations. TDD is a component of Extreme Programming and often used in conjunction with integration tests. The premise of TDD is to test first, code second, and improve (or refactor) last. This forces the software developers to focus on customer specifications first. After each iteration of testing, coding, and refactoring the programmer has a piece of ready to ship code. The rest of this chapter gives the motivation for TDD, shows the steps for TDD, outlines the principles of TDD, provides examples using TDD, and compares the pros and cons of TDD.

Motivation for TDD

There are two main motivations for TDD.

Thumb:Figure 1: Traditional "Cost of Change" curve with the waterfall model superimposed

Principles

TDD follows several principles:

Steps

Thumb:Figure 1: Traditional "Cost of Change" curve with the waterfall model superimposed

Follow these steps:

Imagine how the new code should be called and write the test as if the code already existed. Create the new production code stub. Write just enough code so that it compiles. Run the test. It should fail. This is a calibration measure to ensure that your test is calling the correct code and that the code is not working by accident. This is a meaningful failure, and you expect it to fail.

Write the production code to make the test pass. Keep it simple. Some advocate the hard-coding of the expected return value first to verify that the test correctly detects success. This varies from practitioner to practitioner.If you've written the code so that the test passes as intended, you are finished. You do not have to write more code speculatively. If new functionality is still needed, then another test is needed. Make this one test pass and continue. When the test passes, you might want to run all tests up to this point to build confidence that everything else is still working.

Remove duplication caused by the addition of the new functionality. Make design changes to improve the overall solution. After each refactoring, rerun all the tests to ensure that they all still pass.

Examples

Homework Grades Program

Setup

As a simple example, we are creating a program that keeps track of our homework grades. We envision that we would be able to get the average of these homework grades. Step one: write a test . Let's test an average function

  myHomework = new Homework();
  myHomework.grades = [100, 50];
  assert(myHomework.average(myHomework.grades) == 75);

We will get multiple errors - this test won't even compile (but, that's ok for now). Let's take a look at what will generate error messages:

Now, we fix the first error:

class Homework {

}

Second error:

Homework(void) {

}

Third error:

int * grades;

Fourth error:

int average(int * grades) {
  return 0; // default return value
}

Finally, the test compiles! The code now looks like this:

class Homework {

  int * grades;

  Homework(void) {

  }

  int average(int * grades) {

    return 0; // default return value
 
 }

}

Red Step

Now, we run the test, and the familar red bar of failure greets us (remember the mantra red-green-refactor). The assert fails. The average function needs to actually average something (not just return 0). As we think about averaging the grades, we realize we need to know how many grades are in the int array grades. So, we add to the code:

class Homework {

  int * grades;
  int numGrades;                         // new

  Homework(void) {

  }

  int average(int * grades) {
 
   int avg = 0;                         // new
 
   for(int i = 0; i < numGrades; i++) { // new
     avg += grades[i];                  // new
   }                                    // new
 
   return avg/numGrades;                // new
 
 }

}

Of course, we must remember to change the test to:

  myHomework = new Homework();
  myHomework.grades = [100, 50];
  myHomework.numGrades = 2;
  assert(myHomework.average(myHomework.grades) == 75);

Green Step

Success! We have a green bar when we run it.

Refactoring Step

The last step is refactoring. Perhaps we don't want a grade to be an int? Should it be an unsigned int? For this simple example, there isn't much refactoring to do, but in a larger example there may be multiple areas for improvement.

Refactoring Example

The following example is take from Test-Driven Development by Example by Kent Beck.

Using the TDD process the author created an object with the following structure:

class Dollar {
 
  Dollar(int amount) {
     this.amount = amount;
  }

  void times(int mulitplier) {
    amount *= multiplier;
  }

}

What's wrong with the code above? Look at the following test:

public void testMultiplication() {
  
  Dollar five = new Dollar(5);
  five.times(2);
  assertEquals(10, five.amount);
  five.times(3);
  assertEquals(15, five.amount);

}  

Whenever we perform an operation on a Dollar the value of the Dollar changes. In the test above five isn't 5 after we multiply it by 2, and thus the name five doesn't make sense anymore. How do we change this?

Red Step

First, we change the test.

public void testMultiplication() {
  Dollar five = new Dollar(5);
  Dollar product = five.times(2);
  assertEquals(10, product.amount);
  product = five.times(3);
  assertEquals(15, product.amount);
}

The new test won't compile until we change the code:

Dollar times(int multiplier) {
  amount *= multiplier;
  return null;
}

Now the test compiles, but fails. Once again, we change the code:

Dollar times(int multiplier) {
  return new Dollar(amount * multiplier);
}

Green Step

The test succeeds. So we see that even when we are refactoring we change the test first, then the code. This way we know our refactoring is an improvement.

More Examples

See Test-Driven Development by Example by Kent Beck for more examples.

Characteristics of a Good Unit Test

A good unit test has the following characteristics.

Benefits of TDD

Using TDD has many benefits.

    public void CheckUserCanNotPurchaseItemWithoutBalance(){  
    LoginToTheSystem();       
    SelectAnItemToBuy();       
    MakeSureItemExsitsInStock();       
    MakeSureUserHasBalanceToBuy();      
    RunTheTransaction(); 
 }			

Shortcomings Of TDD

TDD has a few shortcomings:

Conclusion

TDD fundamentaly changes how a developer approaches a programming project. It forces him to focus on the design requirements first by mandating that the developer must write a test for anything before he writes code to do it. It has many benefits including ensuring that the project is well tested and, in some cases, reducing development time. However, it is limited in that it can't test user interfaces or databases directly. Overall, it is a very effective tool for quickly writing well tested code.

References

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox