Unit Tester Product Tour

Introduction

To use the Unit Tester you will need to create a 'test harness' project, this will typically be a Windows Forms application but it does not have to be. In the test harness create a reference to the component that you are going to test and then create a reference to the Unit Tester component. You can then code and run your unit tests from within the test harness using the Unit Tester component.

In this tour of the Blayd Software Unit Tester component we are going to discuss the main classes in the Unit Tester and outline their functionality and use. We won't be able to cover all of the classes in the component or all of the functionality of the classes that we do have space to cover but we will endeavour to give an overview of what is available in the Unit Tester.

Assert Class

Assertions are at the heart of unit testing and you will find that most of the test methods that you code will require assertions to test the pre and post conditions of the members being tested. An assertion has to evaluate to true to succeed, if an assertion, in a test method, fails then the unit test has failed i.e. the expected condition has not been met.

Assert is a static class, all of it's members are static and is the base class from which the TestCase class derives. The TestCase class is the base class from which all test cases should derive, therefore, using the Assert class methods, from within a test case is simple and intuitive. You do not have to use the conventional <type>.<method name> static call syntax for calling the Assert class methods from within a test case, you can just use the method name.

Methods are provided to test for equality, inequality, true, false, null and non-null and there are numerous overloads for each of these tests. When an assertion fails, the Assert class will throw an exception which will cause the test case to terminate will a status of failed. A test case can also be explicitly failed by calling the Assert.Fail method from within a test method. A call to the Fail method will result in an exception being thrown which will cause the test case to terminate will a status of failed.

Each of the Assert class methods has an overload that accepts an error message String. If the assertion fails, the specified message will be assigned to the exception and will, therefore, be included in the test report.

Some typical uses for assertions are as follows:

  • Testing the value of properties that are initialized in a constructor
  • Testing the value of properties that are initialized from configuration settings
  • Testing the value of properties that are initialized from a database
  • Testing the result or return value of a method
  • Failing a test when a required condition is not met

TestCase Class

The Unit Tester component defines the code that tests a unit of functionality as a 'test case'. In most cases a unit of functionality or a test case will relate to a single class, however it does not have to, a class may have several unit tests or test cases created for it.

TestCase is an abstract base class that derives from the Assert class, all unit tests or test cases should derive from the TestCase class to enable their integration with the testing framework. When executing a test run using Unit Tester the testing framework will call each of the test methods in a TestCase derived class instance. To be called by the testing framework each test method in a TestCase derived class must be a public instance method with a name that begins with 'Test'. The test case may contain other methods, private, protected or public with names that do not begin with 'Test', any such methods will not be called by the testing framework. For example, the public instance methods TestMethod1 and TestMethod2 will be called by the testing framework and the methods Method1 and Method2 will not. It is also worth noting that the Unit Tester component is case sensitive, therefore the casing of the term 'Test' should be as shown here, public instance test methods with names such as TESTMethod1 or testMethod2 will not be called by the testing framework.

The virtual OnTestCaseStarting method can be overridden in a derived class to perform any initialization that is required for the test case. The testing framework calls this method before calling any of the 'Test*' methods in the test case.

The virtual OnTestCaseComplete method can be overridden in a derived class to perform any clean up that is required for the test case. The testing framework calls this method when all of the 'Test*' methods in the test case have executed or immediately after the test case has failed.

A test case can be executed either by calling the TestCase.Run method to execute an individual test case or by adding the TestCase derived class to an instance of the TestSuite class and then executing the test suite.

For each execution of a test case the testing framework creates an instance of the TestResult class and associates the class with the test case by adding it to the test case's TestResultCollection. Therefore, if a test case is executed once the TestCase.Results property will contain a single instance of the TestResult class, if a test case is executed twice, in the same testing session, there will be two TestResult instances, etc.

When the execution of a test case is complete the result(s) can be retrieved, in report form, either as a formatted String or alternatively written out to any System.IO.TextWriter derived class. The formatting of a test case result report is defined using the TestResultFormatter class. TestResultFormatter is a static class and is used by the testing framework to format all test results, therefore, changing a TestResultFormatter property will affect the result report output of all test cases.

Each 'Test*' method in a test case can contribute to the test result report by calling one of the following protected TestCase methods:

AddComment
Adds a comment to the test result output for a 'Test*' method.
AddException
Formats the exception properties and adds the details to the test result output for a 'Test*' method. This method should not be used to add an AssertionFailedException, the testing framework handles this type of exception automatically. You do not have to explicitly add any exceptions that may be thrown from the method being tested. As long as the 'Test*' method does not handle any such exceptions they will be caught and reported automatically by the testing framework.
ResultExpected
Used to specify, in the test result output, the expected result of a 'Test*' method. An exception type or a String message can be specified. If the actual result does not match the expectation there could be a nonconformity in the method being tested.
ResultExpectedNoError
Used to specify, in the test result output, that a 'Test*' method is expected to execute without exception. If the actual result does include an exception there could be a nonconformity in the method being tested.

Test result output contributions are added in chronological order, therefore, it is recommended that a consistent approach is used. For example, it makes sense to call either the ResultExpected or the ResultExpectedNoError method at the start of each 'Test*' method so that, when analysing the result report, it is obvious where the nonconformities are. Comments can be added where required but it should be noted that, by default, the testing framework automatically adds a called and complete comment to each 'Test*' method to indicate, in the test report, the point at which the method is called and the point at which it is complete.

TestSuite Class

The Unit Tester component also implements the TestSuite class that can be used to contain, manage and execute a number of test cases. In a test harness that contains several test cases it is possible to execute each of the test cases individually or you can add each of the test cases to an instance of the TestSuite class and execute them all consecutively by calling the TestSuite.Run method.

Instances of the TestCase class can be added to the TestSuite class either by passing an instance of the TestCaseCollection class to the constructor or by adding TestCase instances individually using the TestSuite.TestCases property. When a test suite is executed, each of the TestCase instances in the suite's TestCaseCollection is executed consecutively.

A test suite may be executed synchronously, by calling one of the Run method overloads or asynchronously, by calling one of the BeginRun method overloads. Whether executing a test suite synchronously or asynchronously, a test suite may be executed once, a specified number of times or for a specified period of time.

When running a test suite for a long period, either by specifying the number of runs or by specifying a time period for the run, the ProgessEvent can be used to provide feedback in the test harness user interface. A test suite can be configured, using the FileFlushConfiguration class, to write out the result reports at intervals during a long test run and to clear them from memory once they have been flushed.

Conclusion

A unit testing framework can't, unfortunately, code the test methods for you, however, it can undoubtedly reduce the workload by providing a consistent structure for your tests and a mechanism for running the tests and reporting the results. The Blayd Software Unit Tester component implements a programmatic approach by providing methods that you call from your test methods to interact with the testing framework. It can also be used with any .Net development environment and your preferred test harness user interface.