Avoiding Red/Green/Debug
Thanks to NUnit, unit-testing in a .NET-environment is really easy. With good tools however, it’s still fairly easy to make stupid mistakes. One of the things I’ve learned when constructing unit-tests is that you shouldn’t reuse testing code to create the actual code. Ofcourse that’s obvious, but in some cases, it’s quite easy to do it all wrong and end up in the debugger anyway.
For instance, let’s consider a class Loan (I was going to suggest Mortgage but was afraid that mentioning that word in any kind of internet-based communication would put me on all possible spam-blocklists) that has several properties. You set the YearlyInterestRate or MonthlyInterestRate, which are both read/write-properties and as such need to calculate eachother when you set one of them. So you write an NUnit-test to see whether the calculation is correct:
[Test]
public void YearlyToMonthlyInterestRate()
{
Loan loan = new Loan();
double yearlyrate = 8.7d;
loan.YearlyInterestRate = yearlyrate;
Assert.AreEqual((Math.Pow(1+(yearlyrate/100), 1/12)-1)*100,
loan.MonthlyInterestRate);
}
You construct the object, set the YearlyInterestRate, then calculate the monthly rate and compare that to the value in the corresponding property. Compiling fails, so you construct a dummy class, then you run it, the test fails and you’re back to write the actual class:
using System;
public class Loan
{
private double yearlyrate;
private double monthlyrate;
public double YearlyInterestRate
{
set
{
yearlyrate = value;
monthlyrate = (Math.Pow((1+(yearlyrate/100)), 1/12)-1)*100;
}
}
public double MonthlyInterestRate
{
get
{
return monthlyrate;
}
}
}
Compiling works and running comes up with a green test! This means the code works right? Unfortunately, it doesn’t. We forgot to cast the part where we do 1/12 to double so we end up raising to 0. In both the test and the actual class. 0 is equal to 0, so the test succeeds and the functionality appears to work. Until it’s used in production code and all your client’s customers who opted to pay their interest monthly never have to pay anything.
Even if you didn’t copy/paste the calculation code, if that’s the way you remember to do that calculation, there’s a chance you’ll write the same bug twice anyway. The only way around this is to write multiple tests: one to test whether the correct algorithm is used (at least, if you care about that in the requirements) and another to test whether the results are at least plausible. For instance, we could have added a test to check whether if the yearly rate is non-zero the monthly is as well, or even if both rates are not the same (if they’re non-zero, that is).
What I do for this kind of stuff is just use my calculator to come up with some numbers. If the yearly rate is 8.7%, the monthly rate is around 0.7%. NUnit’s Assert.AreEqual-method has a great overload that allows you to provide a delta:
[Test]
public void YearlyToMonthlyInterestRate2()
{
Loan loan = new Loan();
loan.YearlyInterestRate = 8.7d;
Assert.AreEqual(0.7d, loan.MonthlyInterestRate, 0.01d);
}
This test would have caught the bug. So the answer to a question people ask me a lot when it comes to test-driven development: “But how do you know the tests are correct?” is that you don’t, but by making multiple tests to cover the same functionality you’re getting very close.