Step 7 - Putting it all together
The last section has shown that the new StringBuffer implementation does indeed offer a solution to the potential performance problem of the TestResult class. Now it's time to integrate this new class into the existing codebase of the vbUnit framework. We want to change the design of the code without changing its logical functionality. This is another important application of unit tests. The tests will make sure that changing the code does not alter its functionality.
After the new StringBuffer class has been integrated into the vbUnit framework, I will show how to set up a project structure that allows to test private classes of a project.
Integrating InsertStringBuffer into vbUnit3
The unit test for the TestResult class is pretty straightforward. The code that calls the "ToConsoleString" method is in the class "TestVBUnit" in the project vbUnit3\TestVBUnitFramework\TestVBUnitFramework.vbp:
'TestVBUnit.TestResultFormatter 'test the 'ToString' functions of TestResult and TestError Public Sub TestResultFormatter() Dim EXPECTED_OUTPUT As String EXPECTED_OUTPUT = _ "ERRORS: 1" & vbCrLf & _ "SampleTest.TestError: " & m_divErrorDesc & " [an error]" & vbCrLf & _ "FAILURES: 3" & vbCrLf & _ "SampleTest.TestFail: Verify failed: hello" & vbCrLf & _ "SampleTest.TestCompare: expected '5' but was '3': cmpLongs" & vbCrLf & _ "SampleTest.TestCompare: expected 'abc' but was 'xyz': cmpStrings" & vbCrLf & _ "(3 Tests, 4 Assertions)" & vbCrLf m_suite.AddFixture New SampleTest Dim result As New TestResult Dim error As TestError Dim resultString As String m_test.Run result m_assert.LongsEqual 3, result.NumRunTests, "NumRunTests" m_assert.LongsEqual 4, result.NumAssertions, "NumAssertions" m_assert.LongsEqual 1, result.NumErrors, "NumErrors" resultString = result.ToConsoleString m_assert.StringsEqual EXPECTED_OUTPUT, resultString, "TestOutput" End Sub
This test is just generating a particular TestResult and compares the output of the "ToConsoleString" method with some expected result. This is probably good enough for making sure that the TestResult class is still working after we have integrated the new StringBuffer class. Before doing that, you should make this test fail at least once, just out of habit. Just insert a character somewhere into the expected string and run the TestSuite. Making the test fail will give you more confidence to accept the implications of seeing it suceed. Now change the test back into its original form.
We are now ready to integrate the new class into the "TestResult.ToConsoleString" method. The original form of this method was shown at the beginning of Step 1. Here is the modified form:
'TestResult.ToConsoleString (using StringBuffer class) 'convert this TestResult into a string that will be written to the DOS console 'This is called by the Batch TestRunner Public Function ToConsoleString() As String Dim t As TestError Dim buffer As New InsertStringBuffer If WasSuccessful Then ToConsoleString = "OK (" & NumRunTests & " Tests, " _ & NumAssertions & " Assertions)" & vbCrLf Exit Function End If buffer.Append "ERRORS: " & NumErrors & vbCrLf If NumErrors > 0 Then For Each t In Errors buffer.Append t.ToConsoleErrorString & vbCrLf Next End If buffer.Append "FAILURES: " & NumFailures & vbCrLf If NumFailures > 0 Then For Each t In Failures buffer.Append t.ToConsoleFailureString & vbCrLf Next End If buffer.Append "(" & NumRunTests & " Tests, " _ & NumAssertions & " Assertions)" & vbCrLf ToConsoleString = buffer.result End Function
You can see that this version is already a part of the current vbUnit Framework and that it passes the unit test for the ToConsoleString method.
Testing private classes
The InsertStringBuffer class has become a private class within the vbUnit3 project. This creates a little problem. We want to integrate the unit tests of the InsertStringBuffer class into the complete test suite for the vbUnit framework. However, the project "TestVBUnitFramework.vbp" can only test public objects of the vbUnit Framework. We could just make the InsertStringBuffer class public by setting its Instancing property to "MultiUse". However, it cannot be desireable to make every object in a project public. At the very least, this seems extremely inelegant, since it will create confusion between those interfaces and classes that are really meant to be public, and those that are just public for the sake of unit testing.
One possible solution would be to include the test classes in the main project, along with the production code. This may be reasonable in some cases, but I prefer to separate test code from production code. Hence, my own way of dealing with this situation is to create a separate project that includes the actual classes and modules to be tested. The "TestVBUnit3Priv" project is an example of such a project for testing private classes of the main project. Since the private test project and its main project are intimately related and share the same files, I usually put the private test project in a subfolder of the main project. The public tests can be somewhere else, since they don't share any files with the main project.
Here is an example of a folder structure for the production project, the
public tests, and the private tests:
|vbUnit3.vbp||vbUnit3 \ vbUnitFramework||Contains production code of vbUnit3 framework (all public and private
classes and modules).
Does not contain any test code.
|TestVBUnitFramework.vbp||vbUnit3 \ TestVBUnitFramework||Contains unit tests for the public classes of the vbUnit3
Has a reference to vbUnit3.dll.
|TestVBUnit3Priv.vbp||vbUnit3 \ vbUnitFramework \ Test||Contains unit tests for the private classes of the vbUnit3
The project (TestVBUnit3Priv.vbp) includes the actual private classes and modules from the vbUnitFramework folder that are to be tested.
Should not have a reference to vbUnit3.dll, since it statically includes the classes and modules of that project.
The private test suite can be run on its own. However, we also want to have a single entry point for running all tests. This can be done in the main test suite of the public test project. To include the external test suite, simply add the following line to the "ISuite_Suite" method:
Testing should be done against the public interfaces of a project as much as possible, because that will decrease the coupling between tests and implementation details. The private test project that includes the details of the main project should only be used for those cases where it can prevent making internal objects public.
This step has shown how to set up a folder and project structure for a production project and its public and private tests, using the example of the vbUnit framework itself.
- The public test project has a reference to the main project.
- The private test project includes the actual private classes and modules of the main project. Because of that tight coupling, it is good to put the private test project in a subfolder of the main project.
- Use public tests by default to reduce dependencies on intimate implementation details.
- Use private tests only where they prevent making a private component public.
This concludes the vbUnit3 tutorial for now. We started with an existing piece of code that had some inefficiencies. Then we designed and implemented a new algorithm to deal with this problem (wrapped into a new class), and we integrated the new class into the existing codebase, where unit tests ensured that we did not alter any existing logical functionality. Along the way, the tutorial explained the basics of using vbUnit, and of the unit-test-driven development method in general.
There are still a lot of advanced testing techniques to be discussed, but this tutorial should at least have given you a good start. To continue from here, I recommend the eXtreme Programming websites, including the sites of the other xUnit frameworks. You should understand them by now, and many of the techniques discussed there can be applied to vbUnit.