Unit test framework

Bugs are synonymous with software. Methods to minimize them out can be code-oriented approaches such as OO programming & exception handling, or can be test-oriented such as continuous build & test, automated testing, and unit test frameworks. Here’s an example highlighting the need for a unit test framework.

Suppose you want to write a math library that includes conversions, such as Fahrenheit-to-Celsius, feet-to-meters, etc. Some base functions could be
celcius2fahrenheit( c ), feet2meters( f ), ...
Along with this base code, you can write test procedures using a pre-defined data set as a sanity check for the conversions. In pseudocode,

celcius2fahrenheit( c )
return 1.8 * c + 32

feet2meters( f )
return f*.3048

celcius2fahrenheit_driver( c, expected )
if celcius2fahrenheit(c) == expected
return True
else
print( "error in celcius2fahrenheit(), input = %f", c )
return False

feet2meters_driver( f, expected )
if feet2meters(f) == expected
return True
else
print( "error in feet2meters(), input = %f", c )
return False

test_all() // boundary points make good test data
celcius2fahrenheit_driver( 0,32 ); // freezing
celcius2fahrenheit_driver( 100,212 ); // boiling
celcius2fahrenheit_driver( 37 ,98.6 ); // body temp
feet2meters_driver( 3, .9144 ); // 1 yard
feet2meters_driver( 1, .3048 ); // 1 foot

Evolving to a framework

In this toy problem more code is dedicated to testing than the base functions itself, suggesting that the test code might introduce its own problems and be more trouble than it's worth. However, In full-blown programs, the base code will be more substantive, and test-code:base-code ratio will tip towards base code.

The simplistic approach above starts getting unwieldy when there are tens of base code functions and hundreds of data points. As off balanced as the test-code:base-code ratio is, our test code function "drivers" should do more. It should be informative when there’s an failure by printing out the callstack, handle exceptions, collect pass/fail statistics, etc. Now we begin to see a need for a framework library such as the Java's Junit, Python's PyUnit, .NET's NUnit -- more generally known as the xUnit famiy.

While the above abilities inflate the test code, language specific features can trim it down. One could use asserts or passing function pointers to generalize & consolidate the *_driver() definitions into a single function.

Organizing test code

In organizing the test code within the directory structure of the project code, one must balance two opposing forces:

# Primarily, we want to separate test from base code to prevent them from cluttering the typically very complex base code.
# At the same time, we want to keep them together to easily maintain and synchronize them. If it's a small project, we can keep them in the same directory, or even in the same file.

Usually, our projects are complex so separating them is the best practice. One might keep a test directory tree that mirrors the tree of the base code. This mirror will help keep the test & base in sync, while separating them. The disadvantage is that one has to switch back-and-forth between trees while coding.
 
< Prev   Next >