Content
AutoTest
- Unit testing framework
Reporting
data for diagnostic failing test cases.
Choosing
windows-based test runner
Setting
up and Shutting down test suites
This tool is plain and small, as I designed to be.
The reason behind the design is to put emphasis on the software being designed with the tool, not the tool itself. That is, the tool automates some repetitive tasks and leaves room for what the humans do best, think.
The tool does some tedious tasks for the software designer, but by no means has the tool tried to easy the thinking process of crafting a design.
In the words of Professor Edsger Wybe Dijkstra:
“…tools
have the unfortunate tendency to backfire (Easing the correction of errors
immediately leads to more errors being made)”
Can computing science save the computer industry?, Edsger W. Dijkstra
http://www.cs.utexas.edu/users/EWD/ewd09xx/EWD920.PDF
Just copy AutoTest.dll to one of the following locations:
· Your working folder
· A subfolder named AutoTest within your working folder
The simplistic case: If you are designing a simple calculator class, convey your usage intentions starting as the following:
CalcTest.cs
class CalcTest
: AutoTestSuite
{
public void testSimpleAdd()
{
Calc
calc = new Calc();
Assert( calc.Add(4,3) == 7 );
}
}
AutoTest will execute all the public methods of CalcTest class which first characters are “test”, each such a method is a test case. AutoTest will report results of executing each test case.
Compiling CalcTest.cs now will result with several errors about not knowing AutoTestSuite class type, Calc class type and missing entry point for program. Let’s do the simplest thing that make these errors disappear:
using mdmartin.AutoTest;
// here is defined AutoTestSuite class type
//
here is the simplest code that make to compiling
errors disappear:
class Calc
{
public int Add(int n1, int n2)
{
return 0;
}
}
//
a meaningful program entry point
static void
{
TestSuite suite=new TestSuite("Calc
tests");
suite.Add(new CalcTest());
TestRunner runner = new TextConsoleTestRunner(Console.Out);
runner.Run(suite);
}
Let’s compiling again:
csc /r:AutoTest.dll CalcTest.cs
Execution reports the following:
Often, we want more information about the failing test case, that information could be specified in the test case:
public void testSimpleAdd()
{
Calc
calc = new Calc();
Assert( calc.Add(4,3) == 7 ,"Simple sum: 4+3 != 7");
}
Now execution reports:
By now, we have been using a test runner for console mode, if we want our test runner on windows mode, then we need to change the line:
TestRunner runner = new TextConsoleTestRunner(Console.Out);
With this line:
TestRunner runner = new WinFormTestRunner();
Now, the execution shows the following window:
Selecting the root in the tree view control and clicking on Run button will execute our test cases and report results:
A red box tells us that our current detailed design (source code) is not fulfilling our usage intentions of Calc class type, let’s fix that:
class Calc
{
public int Add(int n1, int n2)
{
return n1+n2;
}
}
Let’s go back to our console mode test runner and see execution results:
Ok, 100% test cases passed! This means our detailed design (source code) fulfill the functional requirements (by now) of our Calc class type.
Our sample testSimpleAdd test case method above creates an instance of Calc class type for exercising the projected functionality, as the test case methods on this test class grow in number, will be annoying to create instances within each test case method. It may be handy to locate a single point to setting up the instances for all test case methods to use, and another single point to release the resources used by the test case methods.
There are a couple of methods from base class AutoTestSuite that can be overridden for such a purpose, like this:
class CalcTest
: AutoTestSuite
{
private Calc itsCalc;
public override void SetUp()
{
itsCalc = new
Calc();
}
public override void ShutDown()
{
itsCalc = null;
// not imperative for memory usage, imperative for other system resources
}
public void testSimpleAdd()
{
Assert( itsCalc.Add(4,3) == 7
,"Simple sum: 4+3 != 7");
}
}
SetUp and ShutDown methods will be executed once each for the entire AutoTestSuite-derived class type, at the beginning and at the end respectively. That is, each test case method is able to use the instance itsCalc, without creating or destroying an instance per test case method.
As development team members work on different functions of the software being designed, they can group their test cases, making up a hierarchy of test cases as deep and broad as they choose.
For example:
class ScientificCalc
: AutoTestSuite
{
public void testSimpleFourierTransform()
{
// test case code for Calc scientific functions
}
public void testSymbolicEvaluation()
{}
}
class MatrixCalc
: AutoTestSuite
{
public void testMatrixAdd() {}
public void testMatrixDivision()
{}
public void testMatrixMultiplication()
{}
}
Setting the test case hierarchy as:
class exe
{
static TestSuite
advancedTest()
{
TestSuite suite=new TestSuite("Calc
advanced tests");
suite.Add(new ScientificCalc());
suite.Add(new MatrixCalc());
return suite;
}
static void
{
TestSuite suite=new TestSuite("Calc
tests");
suite.Add(new CalcTest());
suite.Add(advancedTest());
TestRunner runner = new WinFormTestRunner();
runner.Run(suite);
}
}
Using the WinFormTestRunner looks like:
If there are AutoTestSuite-derived class types defined in your testing assembly, certainly you want them to be executed; if the test case hierarchy configuration is conveyed by static methods like advancedTest above, then it may be handy to say that kind of decisions in just one place and saying something like “setup test case hierarchy from class types defined in calling assembly”; avoiding to configure the hierarchy at the program entry point.
If this is the case, then you may use the static method SuiteFromCallingAssembly from AutoTestSuite class type like this:
class exe
{
static void
{
TestRunner runner = new WinFormTestRunner();
runner.Run(AutoTestSuite.SuiteFromCallingAssembly());
}
}
All the AutoTestSuite-derived class types will be included in your test case hierarchy. For grouping define a TestSuite-derived class type that group the test suites as needed in a TestSuite instance which must be returned from a public static ‘Suite’ method, like the following:
class AdvancedTest
: TestSuite
{
public static TestSuite Suite()
{
TestSuite suite=new TestSuite("Calc
advanced tests");
suite.Add(new ScientificCalc());
suite.Add(new MatrixCalc());
return suite;
}
}
Now, the execution inspects the calling assembly and configures the test case hierarchy from the class types defined within.
Just as Mr. Churchill said: “…this is not the end, this is not even the beginning of the end…; perhaps this is the end of the beginning”
It is up to you; give an honorable try to test-first design techniques using supporting tools like this one. It could make a big difference in the quality of your software designs.
Several unit testing frameworks designs http://www.xprogramming.com/software.htm
Code Complete http://www.stevemcconnell.com/cc.htm
Is design dead? http://martinfowler.com/articles/designDead.html
Continuous Integration http://martinfowler.com/articles/continuousIntegration.html
Avoiding Repetition http://martinfowler.com/articles/repetition.pdf
Best regards,