Tomorrow’s CharJUG presentation is on JUnit. JUnit, for those of you who are not aware, is a unit-testing framework for Java. (Well, it is officially labeled as a regression-testing framework, but you do that through Unit Tests). It is part of the xUnit family of Unit Testing frameworks, where you replace the x with some other language of choice such as C++ (cppunit), .NET (NUnit), Javascript (JsUnit), etc.
The concept behind these tools is that you write a test in your chosen language which results in some sort of check, like Assert.IsTrue(customer.DidPay)
. Then, the framework runs these tests and spits back a result. When using the GUI versions of these tools it returns a red bar if any test failed, and a green bar if they all passed.
Now, writing Unit Tests seems about as exciting as replacing every instance of the letter q
that your uber-3l1t3 developer used as the variable name for a critical component, manually. But while there are many reasons one would think of testing as an activity for those strange QA guys, I want to introduce the idea of using tests to design your code.
I’ll start with a simple example of how to use a test to design my code. Let’s say I want to calculate sales tax for an item. Being the good OO developer I am, I could just write something like:
private const double SALES_TAX = 7.5;
public double CalculateSalesTax(double amount)
{
return amount * SALES_TAX;
}
Four lines of code later, I’m done. But, let’s say I wanted to let tests drive the design instead. I would start out with something like:
public void TestSalesTaxCalculatedCorrectly()
{
double amount = 10.00;
double expectedSalesTax = 0.75;
double salesTax = CalculateSalesTax(amount);
Assert.AreEqual(expectedSalesTax, salesTax);
}
But this wouldn’t compile, because we hadn’t written our CalculateSalesTax method yet. But now we would, and it would look just like it did above.
What did we gain? Nothing much yet. But now, let’s say our app is going to be used in two different states, so the sales tax is different. We now add a test for Florida:
public void TestSalesTaxCalculatedCorrectlyFlorida()
{
double amount = 10.00;
double expectedSalesTax = 0.65;
double salesTax = CalculateSalesTax(amount);
Assert.AreEqual(expectedSalesTax, salesTax);
}
Which fails, because our CalculateSalesTax method had the Sales tax hardcoded. So we know we need to modify our sales tax method to calculate based on state:
public double CalculateSalesTax(double amount, State state)
{
if(state == State.NORTH_CAROLINA)
{
return amount * 7.5;
}
else
{
return amount * 6.5;
}
}
Which now causes our tests to fail, because they aren’t passing in the state. So we modify them:
public void TestSalesTaxCalculatedCorrectlyNorthCarolina()
{
double amount = 10.00;
double expectedSalesTax = 0.75;
double salesTax = CalculateSalesTax(amount, State.NORTH_CAROLINA);
Assert.AreEqual(expectedSalesTax, salesTax);
}
public void TestSalesTaxCalculatedCorrectlyFlorida()
{
double amount = 10.00;
double expectedSalesTax = 0.65;
double salesTax = CalculateSalesTax(amount, State.FLORIDA);
Assert.AreEqual(expectedSalesTax, salesTax);
}
And now all of our tests pass. So now what have we gained? The confidence and knowledge that our CalculateSalesTax method correctly calculates Sales Tax for Florida and North Carolina. What else have we gained? The lowering of the test barrier. You’ve got the tests written, so if you think of another condition that you want to check, you just add a test for it. Finally, if you ever modify the CalculateSalesTax method, say to have the sales tax rates pulled from a more dynamic source, you can know that you didn’t break anything, because you have the tests to show it.
This concept of letting your tests drive your design is called, aptly enough, Test-Driven Development. Of course, TDD is part of the whole Extreme Programming movement, but this, along with automated builds, is one of the things you can do today, with little impact to your skeptical developing peers, that will have a big impact on producing quality, bug free code.