Unit Testing Notes


This entry is in response to a question I saw recently in a forum, asking about unit testing and how one sets up a database, etc. The main point was about unit testing and whether one should only test a single routine.

Unit Testing

 
First, let’s understand that unit testing, if you are a purist, is testing a single routine. This is a great ideal, but often hard to stick to completely, unless you follow patterns that make "injection" of helper routines easy to accomplish. I am not saying you should not try, but there are some cases where injection or mocking is a bit of overkill (I am sure some purists will stomp me for this one).
 
If you would like the pure route, writing the test first will help you determine where to invert control (supply the next step rather than simply hook to it?), where to set up factory methods so you can mock the next step and where to truly inject some code to test in isolation. Not all of us have this liberty.
 
I have an application that, for some unknown reason, the developer thought it would be a good idea to store both lat and long in a single comma separated string rather than separating into lat and long. I have added Lat/Long fields to the table, but I still have to support the legacy application for the time being. So, I have the following routine:
 
private static string CreateLatLongString(double lat, double lng)
{
    StringBuilder builder = new StringBuilder();
    builder.Append(lat);
    builder.Append(‘,’);
    builder.Append(lng);
    return builder.ToString();
}
This routine is extremely easy to test. Here is a quick down and dirty example of a test (no the greatest, mind you, but it suffices).
 
[TestMethod()]
[
DeploymentItem("Microtrak.KeyPer.Service.dll")]
public void Lot_CreateLatLongString()
{
   
double lat = 33.1234F;
   
double lng = -84.1234F;
   
string expected = lat.ToString() + "," + lng.ToString();
   
string actual = Lot_Accessor.CreateLatLongString(lat, lng);
   
Assert.AreEqual(expected, actual);
}
 
I would generally include a few other tests on a routine, but there is really no reason to do this in this case.
 
Now, there are a couple of routines that use the CreateLatLongString routine. And, I have coded numerous tests for them, but I have never tried to remove this bit from the call to the other routines, so I am including the CreateLatLongString in my tests for the other routines. I see nothing wrong with this, as I have a test for the routine, and it really does not do much. In addition, it is going to die a horrible death as soon as I can have the insanity removed from the other application, so I don’t see the wisdom in spending the time to mock up this "dependency".
 
There are times, however, where mocking does make sense to keep a test isolated to a single routine. One example, which is used in this project, is mocking up a web service call. For the first iteration, as I am refactoring code rather than writing from scratch, I have created a simple "web service" that returns canned answers. It is not the best mock, but it works fine. To alter the software to take the mock, I have a couple of choices.
 
One is to supply the web service URL to the routine.
 
 //Original
public static int CreateNewDealer(string dealerName)
{
   
//routine implementation ….
    return CreateDealer(dealerName);
}
//updated
public static int CreateNewDealer(string dealerName, string webServiceUrl)
{
   
//routine implementation ….
    return CreateDealer(dealerName);
}
 
This is, by far, one of the easiest ways to "invert" the control of the application. Another way to handle this is via config, where you specify the class that takes the action in the config file. I am not fond of this as a test method, although it is useful if you have multiple environments where a config change can set up the environment’s unique needs. I would not move to configuration alone as a tested method, although I have seen it done.
 
Another method is to create a global type of class. This is a dual edged sword in many ways, as you can bite yourself quite easily if you are not careful. But, this does work nicely with the configuration model, as you can override the behavior of the global object by supplying in your own dependencies. You can then isolate your routines quite nicely.
 

Integration Tests

Now, I know some people who believe one should not use a unit test framework for integration testing (tests that test the system, rather than a small piece of code). I disagree with this, although I do agree you should divide out any integration tests from your unit tests. One nice way to set up an integration test is to test the deepest piece of code first, then the routines that call that bit of code and so on. Your final tests can end up testing a complete use case rather nicely.

Clean up

If you do step into full integration (or regression) tests with a unit test framework, you will have to clean up after yourself. As an example, here is a cleanup routine that cleans up the database. It is only designed to work against the test database, so I can have predictable results. IN fact, if I am really anal about it, I can use the setup to create the database from scratch. I have not taken it this far yet.

Here is a small routine that gets rid of my crud and resets the identity value, a necessity for an integration test that inserts into a database table with IDENTITY specified.

[TestCleanup()]
public void MyTestCleanup()
{
   
if(cleanUpServerDatabase)
    {
        CleanUpDatabase(
databaseHelper.GetConnectionString(KeyperServerType.Server));
        cleanUpServerDatabase =
false;
    }
   
if(cleanUpLocalDatabase)
    {
        CleanUpDatabase(
databaseHelper.GetConnectionString(KeyperServerType.Local));
        cleanUpLocalDatabase =
false;
    }
}

private void CleanUpDatabase(string connectionString)
{
   
string sql = "DELETE FROM Dealers WHERE Id_Dealer > 1";

    SqlHelper.ExecuteNonQuery(connectionString, CommandType.Text, sql);

    sql = "dbcc checkident (‘dealers’, reseed, 1);";
   
SqlHelper.ExecuteNonQuery(connectionString, CommandType.Text, sql);
}

In case you are wondering, I am using the older data application block here. The reason leads to a rather lengthy explanation, so I will drop it.
 

Summary

In an ideal world, you always write tests first. You can use use cases as your road map for your tests, so you end up testing the functionality expected. Start with your helper routines and work your way out. This allows you to both set up unit tests and integration/regression tests. While some may disagree, I find that both are critical to long-term survival.

Peace and Grace,
Greg

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: