Step 2 - The problem with String Concatenation

Now that we have the empty Test Project, we can add some code to illustrate the string problem. Let's do this in a new class, so that I can show you how to add a new Fixture:

Adding a new Test Fixture

Click on "Project / Add Class Module" and select "vbUnit TestFixture":


Figure 5: New Fixture, in Project / Add Class Module

Rename the Fixture class into "Step2" and add it to the TestSuite by adding the following line to the ISuite_Suite method:

'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
  suite.AddFixture New Step2
  
  Set ISuite_Suite = suite
End Function

Run the project again in the vbUnit window. You should now get two failed assertions, one from the Step1 fixture, and one from Step2:


Figure 6: second run, now with two default fixtures

 

A little performance measurement 

Now let's actually do something useful with the fixture class and add some code. The problem to be examined is String concatenation. Let's examine the problem with a little performance analysis. But before that, comment out the line suite.AddFixture New Step1 in your TestSuite, since we won't need the Step1 fixture for now. After that, your TestSuite should look like this:

'vbUnitTutorialSuite.Suite

Private Function ISuite_Suite() As ITest
Dim suite As New TestSuite
  suite.SuiteName = "vbUnit Tutorial Suite"
  
'  suite.AddFixture New Step1   'disable this fixture for now
  suite.AddFixture New Step2
  
  Set ISuite_Suite = suite
End Function

Now, replace the code in the Step2 fixture with the following: 

'Step2.cls

Option Explicit

Implements IFixture

Private m_assert As IAssert
Private m_timer As PerformanceCounter

Private Sub IFixture_Setup(assert As IAssert)
  Set m_assert = assert
  Set m_timer = New PerformanceCounter
  m_assert.EnablePrintMsg
End Sub

Private Sub IFixture_TearDown()
End Sub

Public Sub TestConcat1000Strings()
Dim str As String
Dim l As Long
  m_timer.Start
  
  For l = 0 To 1000
    str = str & "This is the " & l & "nd line in the string." _
        & vbCrLf
  Next l
  
  m_timer.Finish
  
  m_assert.PrintMsg m_timer.ToString, "msg1"
End Sub

Public Sub TestConcat2000Strings()
Dim str As String
Dim l As Long
  m_timer.Start
  
  For l = 0 To 2000
    str = str & "This is the " & l & "nd line in the string." _
        & vbCrLf
  Next l
  
  m_timer.Finish
  
  m_assert.PrintMsg m_timer.ToString, "msg1"
End Sub

This code is running two TestMethods, TestConcat1000Strings and TestConcat2000Strings. Both methods use a timer to measure the amount of time it takes to concatenate a number of strings. The resulting time is sent to the Results window with m_assert.PrintMsg. Printing messages is purely for the benefit of the human programmer, and they have no effect on the success or failure of the test. That's ok because at this point we are using vbUnit to experiment with some pieces of code instead of writing Unit Tests.

Save your project and run it in the TestRunner. On a relatively old machine, I get the following result:

   MESSAGES: 2  
Step2.TestConcat1000Strings: 640 ms
Step2.TestConcat2000Strings: 3112 ms

In other words, concatenating two times as many strings takes about five times as long. If you are feeling adventurous or have a very powerful machine, try increasing the number of iterations to 10000 and 20000, and then watch the memory consumption of VB6.EXE in the Task Manager (Warning: SAVE EVERYTHING before you try this).

On my machine, I got:
 1000 Strings:     640 ms =  0.01 Minutes
10000 Strings: 152,189 ms =  2.5  Minutes
20000 Strings: 632,876 ms = 10.5  Minutes

Concatenating 20000 strings takes about 1000 times longer than concatenating 1000 strings, even though the task size is only increased by a factor of 20. This algorithm belongs to the worst of its kind!
Although these examples are a little extreme, they illustrate a potential performance problem for the vbUnit application. Even with relatively moderate result sets, there is already a noticeable delay when the result is formatted.

The explanation for this poor performance is simple: When VB processes an expression of the form str = str & "some text", it calculates the new length of str, allocates a corresponding amount of memory, copies str + "some text" into the newly allocated memory, and then releases the memory for the previous copy of str. Hence, adding a single character to a string of 5000 characters will result in 5001 characters being copied. Adding a single character to a string of 5000 characters twice in two successive operations will result in 10003 characters being copied. In addition to the copying, there is the overhead of memory management. Incremental string concatenation in VB is really expensive!

In the next step we will look at ways to overcome this problem.

 

Summary

This step has shown how to use vbUnit to quickly run and compare some pieces of code without the need to write a user interface based on the usual "Form1".

  • add a new Fixture class with "Project / Add Class Module" and select "vbUnit TestFixture"
  • then register the name of the new fixture in the ISuite_Suite method of your TestSuite
  • vbUnit is not just for unit testing, it can also be used for quick prototypes and experiments
  • send messages to the Results window with m_assert.PrintMsg
  • PrintMsg must be enabled with m_assert.EnablePrintMsg. This can be located in IFixture_Setup