Where should your unit tests go?

by Venkat Subramaniam

Related link: http://www.agiledeveloper.com/download.aspx



Where does your unit test go?

Test Driven Development and Test First Development have a number of benefits. It is more of an act of design than an act of verification as I have discussed in a three part series at http://www.agiledeveloper.com/articles/TDDPartI.pdf.

Where should the tests go? While writing test cases, you may have to access not only the public members, but also internal or package friendly members as well. In Java, you would want to write your test cases in the same package. In the case of .NET, you would put them in the same project. If you do not want to deploy the test cases, you can exclude them from your release build.

What if you need to test a private method or property of a class? First question to ask is, why would you want to test such methods or properties? Generally speaking, may be not. However, say I am writing an Order class and as part of its checkout method, I need to charge a credit card. The method to charge the credit card most likely is going to be private as it is an implementation and not something that the Order class would expose as public.

Why test it? You would certainly want to make sure you have handled the failure cases well. What if the card was invalid, or the charge did not go though or even you could not communicate with the service that authorized the credit card.

How do you test private methods in this case? Before I get into that, let me say that I am not suggesting that you write all your test cases like this. I would use this as an exception than a norm.

Why not write your test case that needs to test your private methods as an inner class in Java and nested class in .NET. Let’s try that.

Here is the .NET example:


public class Order
{
private string Charge(Card theCard, double amount)
{ //...
return "chargeCode...";
}

[TestFixture]
public class OrderChargeTest
{
private Order theOrder;
private Card theCard;
private double AMOUNT1 = 121.12;

[SetUp]
public void SetUp()
{
theOrder = new Order();
theCard = new Card(...);
}

public void TestCharge()
{
theOrder.Charge(theCard, AMOUNT1);
}
}
}


You can write more test to make sure the code behaves well for failure of the Charge.

Here is the Java code that does the same thing as above:


public class Order
{
private String Charge(Card theCard, double amount)
{ //...
return "chargeCode...";
}

public class OrderChargeTest extends TestCase
{
private Order theOrder;
private Card theCard;
private double AMOUNT1 = 121.12;

public void setUp()
{
theOrder = new Order();
theCard = new Card(...);
}

public void TestCharge()
{
theOrder.Charge(theCard, AMOUNT1);
}
}
}


In addition, I have a TestIt class as shown below:


public class TestIt
{
public static void main(String[] args)
{
junit.swingui.TestRunner.run(Order.OrderChargeTest.class);
}
}


When I run JUnit, I get the following error:

junit.framework.AssertionFailedError: Class Order$OrderChargeTest has no public constructor TestCase(String name) or TestCase()

The reason for this error is inner classes in Java are special. The constructor of the inner class is synthesized with a reference to the host class (the class in which it is contained). As a result, JUnit is complaining that it could not find a constructor which takes no arguments. Of the four types of inner classes (regular, local, anonymous and static) only the static inner class’ constructor is not synthesized with a reference to the host class. In Java, we should use static inner class instead of regular inner class here.

Modifying the code as


public class Order
{
private String Charge(Card theCard, double amount)
{ //...
return "chargeCode...";
}

public static class OrderChargeTest extends TestCase
{
private Order theOrder;
private Card theCard;
private double AMOUNT1 = 121.12;

public void setUp()
{
theOrder = new Order();
theCard = new Card(...);
}

public void TestCharge()
{
theOrder.Charge(theCard, AMOUNT1);
}
}
}


takes care of the problem.

On rare occasions, writing your test cases as nested classes in .NET or static inner classes in Java may come in handy. One down side is it is hard to exclude these from the release build. In the next version of .NET, with the introduction of partial classes, that should not be a problem.


What are your thoughts on TDD/TFC and where tests should go


5 Comments

TomDavies
2004-12-07 18:50:09
Use a mock
Your Order implementation will somehow gain access to a Card. Configure your system so that this Card is a mock card, and then call whatever (public) method on Order you want to test. After the call, check the mock Card to see that the appropriate things happened to it.


Tom

trey_hutcheson
2004-12-10 08:02:35
Excluding nested classes from release build; white-box vs black box
Venkat,
In dotnet, one may exclude the nested test fixture from the release build by using the #if DEBUG directive. When you change the project configuration to Release, this code will not be compiled.


However, if the Release configuration has the DEBUG symbol defined, obviously what I just said is not true.


I know the point of your post was not to discuss the merits of black-box vs white-box testing in depth, but could you take a moment to elaborate on your opinions "in general" to the different approaches?

VenkatSubramaniam
2004-12-10 08:12:55
Use a mock
Tom,


No double there is benefit to using mock and
in fact I will certainly use mock for the credit card charge processor. However, that will not
fully solve the problem. The concern is about
the need that may still exist to test some private methods. If there are reasons to test private methods, the suggestion is to use the approch mentioned.

VenkatSubramaniam
2004-12-10 08:17:17
Excluding nested classes from release build; white-box vs black box
Trey,


Good point. You are correct about the use
of #if and also about you concern of
it still getting through in release.
Instead of #if DEBUG, you may use
#if BUILD_TESTS or some thing like that.
This will be very explicit and also removes
the concern you stated.


I will talk about the different approaches soon
in another post. Thanks!


2006-12-03 06:21:05