Step 1 - Creating a new Test Project
Premature optimization is the root of all evil.
- Donald Knuth
It's easier to make a maintainable program fast,
than to make a fast program maintainable.
- Extreme Programming Philosophy
I have included these quotes because I'm beginning this tutorial with a look at performance. This is because I want to give you a real example of working with vbUnit instead of some contrived toy problem, so I'm documenting the final stages of working on a particular product - vbUnit3 itself. This version of vbUnit is pretty much at the end of its development cycle, and performance is the last thing I'm looking at. So far I have spent all my time making the codebase maintainable, and to make it work. Now, at the end of development, I'm going to make it fast by looking at those areas where performance turns out to be a concern. So bear with me and keep in mind that this instance of optimization is hardly premature. This tutorial should definitely not encourage you to start a project by optimizing it!
As expected, the main area that may cause some performance problems is the ToConsoleString function of the TestResult class, since it uses string concatenation (don't worry too much about the details of this code at this point):
'TestResult.ToConsoleString method (vbUnit3.03.03) '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 ToConsoleString = "" If WasSuccessful Then ToConsoleString = ToConsoleString + "OK (" & NumRunTests _ & " Tests, " & NumAssertions & " Assertions)" _ & vbCrLf Exit Function End If ToConsoleString = ToConsoleString + "ERRORS: " & NumErrors _ & vbCrLf If NumErrors > 0 Then For Each t In Errors ToConsoleString = ToConsoleString & t.ToConsoleErrorString _ & vbCrLf Next End If ToConsoleString = ToConsoleString + "FAILURES: " & NumFailures _ & vbCrLf If NumFailures > 0 Then For Each t In Failures ToConsoleString = ToConsoleString & t.ToConsoleFailureString _ & vbCrLf Next End If ToConsoleString = ToConsoleString & "(" & NumRunTests _ & " Tests, " & NumAssertions & " Assertions)" & vbCrLf End Function
If you have done any amount of work with VB, you should have seen this kind of cumulative string concatenation before. The function iterates through the Errors and Failures collection of the TestResult and appends their formatted output to the return value of the function.
Since this is a pretty long function, let's create a simplified version that illustrates the essence of what is happening here. We will do this in a new vbUnit project to isolate the problem. (I assume that you have already installed vbUnit3).
In the VB6 IDE, click on "File / New Project" and select "vbUnit TestDll":
Figure 1: File / New Project
VB will create a new project with two default classes, a TestSuite and an empty TestFixture. First, you should rename the default name of the project, so go to the project properties and change the generic Project Name and the Project Description into something more specific. For example, I chose "vbUnitTutorial" as Project Name, and "vbUnit Tutorial" as Description:
Figure 2: Project / Properties
Now you should also rename the two objects. Hence, rename the TestSuite into "vbUnitTutorialSuite" and the Fixture into "Step1". Now save your project into a folder of your choice. This should leave you with the following set of classes:
Figure 3: Project Outline
Finally, in the TestSuite, replace the generic "vbUnitTestFixture" with the actual name of your new fixture, in this case "Step1":
'vbUnitTutorialSuite.cls Option Explicit Implements ISuite Private Function ISuite_Suite() As ITest Dim suite As New TestSuite suite.SuiteName = "vbUnit Tutorial Suite" 'TODO: Add your Fixtures here suite.AddFixture New Step1 Set ISuite_Suite = suite End Function
Now the basic setup of the new TestProject should be complete, and we can test if it is working so far. Click an "Add-Ins / Show vbUnit window" if the vbUnit window isn't visible yet and make sure that the correct name of the TestSuite (vbUnitTutorial.vbUnitTutorialSuite) is selected next to the run button. Also, make sure that "run in debugger" is selected, since we have not yet compiled the project at this time.
Before you start the test, make sure that the error mode is set to "Break on Unhandled errors". This is done in Tools / Options / General:
Figure 3: Error Trapping (in Tools / Options / General)
Now, click on the "Run" button of the vbUnit window. You should get the following result:
Figure 4: first run of the new project
The new fixture class has one default TestMethod that generates a failed assertion, just to make sure that everything is working. Double-click on the failure message in the vbUnit window, and vbUnit will highlight the corresponding line in the code window, where the failed assertion was thrown:
'Step1.cls Option Explicit Implements IFixture Private m_assert As IAssert 'Setup and TearDown will be called before and after each test method Private Sub IFixture_Setup(assert As IAssert) Set m_assert = assert End Sub Private Sub IFixture_TearDown() End Sub 'TODO: Add your test methods here Public Sub TestFail() m_assert.Verify False, "hello" End Sub
What is happening so far?
Unit Tests are organized into TestSuites and Fixtures.
A TestSuite is a collection of one or more Fixtures.
A Fixture is a class that allows tests to be run against a certain configuration of objects (a "Fixture"). In the most basic case, it is simply a way to group a set of TestMethods that have a common theme into a single class.
A TestMethod is a public method of a Fixture class, and it has a name that starts with "Test". Each of those methods will be called by the vbUnit framework when the TestSuite is run.
To become a Fixture, a class must implement the interface "IFixture", which has the two methods "IFixture_Setup" and "IFixture_TearDown". These methods are called before and after each individual TestMethod. The important thing for now is that the Setup method passes along a reference to an assert object, which should be stored as a member variable of the Fixture. The default name of this variable is m_assert, but you can change this into anything you like.
Using m_assert, the TestMethods can make Assertions. By doing so, the test verifies that its world is in a particular state, and if that is not so, the test will fail. In the default example, there is only a single TestMethod, "TestFail", and it simply calls m_assert.Verify with the argument "False", so this will always generate a failure:
m_assert.Verify False, "hello"
The second argument of the assertion ("hello") is a message. This message is used to identify a failed assertion. It is used in two ways. First, it is displayed in the results window, so that you can visually identify the source of a failed assertion by looking at the results. Second, when you double-click on a failed assertion in the results window, vbUnit will try to find and highlight the corresponding code line where the assertion occured. This is done by matching the assertion message, so it is important that you use unique assertion messages if you want to use the code highlighting feature.
If you have followed me up to here and your basic setup is working, we can move on to the next step.
This step has shown how to set up a new vbUnit TestProject and how to run it for the first time.
- create a new vbUnit Project with "File / New Project" and chose "vbUnit TestDll"
- change the generic names of the Project and the two classes
- adjust the name of the Fixture class in the TestSuite at the place where it says "suite.AddFixture"
- a TestProject usually contains one TestSuite, which contains several Fixture classes
- a Fixture allows a group of related tests to be run
- a TestMethod is a is a public method of a Fixture whose name starts with "Test"
- Fixture.Setup will be called before each TestMethod, Fixture.TearDown thereafter
- the m_assert member is used to make assertions
- the message argument of an assertion is used to find the line of code after a double-click on a failed assertion
- to make the code-line matching work, the assertion message should be a unique literal string