Unit Testing

Unit testing is quite possibly the single best practice for ensuring that your code is bug free (or very nearly bug free!). The basic idea is very simple - as you write application code, you write other test code which exercises the application code to ensure that it is operating correctly.

Sounds simple, and for the most part it is - things get tricky when dealing with interfaces and databinding but most "standard" objects can be tested very easily - if you use good tools!

Testing Zen

Regardless of the framework you use, the concept is the same. You create a "test" class, which exposes a set of public methods, which execute the code under test, and report success or failure. The idea is to build up a suite of tests for all the functions in your application, and then re-run them every time you do a build. This way you catch any regression very quickly. When you do find a bug in your code, the first thing you do is create a test which replicates the bug, and then apply the fix. By adding the fix to your test suite you can be sure that it will always be checked.

Unit Testing Tools

Since unit testing is so widely used, there are a number of tools and frameworks which can help you manage your testing.

Nunit

The most widely known .NET testing framework is NUnit. This is a port of the popular Java unit testing tool - Junit. The examples on this page use NUnit, and the book "Test Driven Development in Microsoft .NET" also uses NUnit.

TestDriven.Net

TestDriven.Net is another toolkit & unit testing framework. While it still operates in the same way as NUnit, it includes a number of Visual Studio tools, and some extensions which allow for declarative data driven testing. It costs $95 for the "Professional" version, and $135 for the "Enterprise" version. Although I've never used it, it is a very higly regarded product,

Visual Studio / MSTest

Visual Studio 2005 now has integrated testing tools. While not exactly compatible with formal "Test Driven Development", they are quite nice. We are now using this for all our on-going development.

Enough Talk - Examples

The example I'm going to walk through can be downloaded here. This is a pair of Visual Studio 2003 projects, which have references to ArcGIS 9.1

The sample is pretty simple in some respects - I've created a utility assembly for working with geometries. The GeometryOperations class has a Shared (static) method called SplitPolygon. The code for the method is shown below.

Click to View Code

I'm not going to get into the details of what this code does - suffice to say that you pass in a polygon and a line, and the polgon is cut into two pieces by the line. In order to test this code, we must think about all the good iputs, as well as all the bad inputs. We want to be sure to test not only "ideal" conditions, but also error conditions.

As a bonus this sameple code also contains methods for serializing and de-serializing IGeometry objects to Xml. Very handy for testing, and for other uses. Have fun with it!

Creating Tests

I tend to put tests in a separate test assembly (ArcDeveloper.Utilities.Tests in this case), but you can put them where ever you like - some people like to keep the tests in the same file as the class under test. It really does not matter.

The key to creating tests lies in the use of Attribures. Attributes are used to "decorate" a class/property/method with extended information that the .NET framework can use. Unit testing frameworks use these attributes when they execute the test methods.

The code below shows the bare-bones of a test class. Note the <TextFixture()> attribute on the class, and the <Test()> attribute on the SplitPolygonTest method.

<TestFixture()> _

Public Class PolySplitTest

 

    <Test()> _

    Public Sub SplitPolygonTest()

 

    End Sub

End Class

From here, we simply add in some code to actually test our SplitPolygon method on the GeometryOperations class.

Click to View Code

This first test passes in good data, so the function goes ahead and splits the polygon using the line. but we also want to test the case where bad data is fed to the function. The "bad data" scenarios are:

  1. Line does not intersect polygon
  2. Line entirely inside polygon
  3. Line ends inside polygon
  4. Line starts inside polygon
  5. Line is null
  6. Polygon is null

Thus, we want to ensure that we have tests which cover each scenario. In the SplitPolygon function, the code checks for each of these conditions and throws an ArgumentException containing a message explaining the problem. The code below shows a test written to exercide one of these conditions. Note the Attribute now specifies the type of exception that is expected.

Click to View Code

Running Tests

The NUnit GUI is used to actually execute the tests. This application looks at the assembly that contains the tests, reads the attributes (via reflection) and presents a list of the tests in the assembly. The user then selects the test to execute, and the application reports success or failure as Green or Red "lights".

NUnit: Green Lights NUnit: Red Lights

Tests run in NUnit (click to enlarge)

Summary

In my experience, writing tests may take more time during coding, but it saves much more time during debugging. I highly recommend learning a unit testing framework, and using it as much as possible. This article is a very high-level introduction to the concepts of unit testing, and while it can get you started, there are a myriad of other related topics - code coverage, mock objects, continuious integration and "test-driven development" to name a few. There are also challenges involved in testing interfaces, and data binding - maybe I'll add another article on these topics if people request it!

Resources: