VBA Doc Tests: Advanced Features
Let's take our doc tests to the next level. I'll show you how to use my custom DocTests function to test classes, private functions, and error conditions.
In my previous post, I introduced my DocTests
function. I reviewed the basic functionality, but alluded to some other advanced features. Let's talk about those features now.
Testing Class Properties/Functions
When I initially built the DocTests feature, I used the Access Application's Eval function to evaluate the expressions inside my doc test comments. That's nice, but it only works to evaluate simple expressions that result in a string/number/date.
This limitation meant I could not test properties and methods of class modules. That's because we first need to create an instance of the class before we can access any of its properties or methods (unless we set the class's PreDeclaredID attribute to True, but that's not something we should be doing for most classes).
To get around this limitation, I do some VBA Extensibility magic to create a temporary code module, run the tests, and then destroy the code module. This is the sort of thing that can cause corruption in your .mdb or .accdb, so it's important that you've backed up your database before using this feature. It's even better if you've got your database in version control.
Using this method would have worked for all of my basic doc tests as a full replacement for using the Eval()
function. Due to its danger, though, I decided I wanted to use Eval()
whenever possible, and only use this method when it was absolutely necessary.
I accomplished this by introducing a separate syntax for these kinds of doc tests. Instead of the three greater-than signs (>>>
) that I use to indicate a basic doc test executed via Eval()
, this method requires four greater-than signs (>>>>
).
Here's an example of when/how I might use this. Save the following code in a class module named oString
and then run DocTests to see it in action. Note that space-colon-space characters ( :
) are used as line separators.
Private mValue As String
Public Property Get Value() As String
Value = mValue
End Property
Public Property Let Value(NewValue As String)
mValue = NewValue
End Property
'>>>> Dim Txt As New oString : Txt = "FooBar" : Txt.StartsWith("Foo")
' True
'>>>> Dim Txt As New oString : Txt = "FooBar" : Txt.StartsWith("Bar")
' False
Public Function StartsWith(Text As String) As Boolean
StartsWith = (Left(Me.Value, Len(Text)) = Text)
End Function
Testing Errors Raised
As I've written about in the past, not all errors should be handled inside the procedures where they occur. In many cases, it's better for those errors to "bubble up" to the calling routine. To test this behavior, I needed a syntax to check for errors. Here's what I came up with:
'--== Module1 ==--
'>>>> 1/0
' 1
'>>>> 2/0
' #ERROR#
'>>>> 3/0
' #ERROR# 11
'--== Immediate Window ==--
DocTests
Module1: 1/0 evaluates to: #ERROR# 11 Expected: 1
Tests passed: 2 of 3
?AccessError(11)
Division by zero
To check for an expected error, enter ' #ERROR#
on the line following the tested expression. To check for a specific error number, add the number just after the #ERROR# string, ' #ERROR 11
.
Note also that this feature requires what I refer to as complex evaluation. Basically, it means that we can't just use the Access.Eval()
function. Instead, we have to create and destroy a temporary code module. And that means we need to use four greater-than signs (>>>>
) to trigger the complex evaluation.
Testing Private Functions
Another advanced scenario that the DocTests function supports is testing private functions. This requires an asterisk before the function name (*
). Like the complex evaluation scenarios above, this method makes temporary changes to the code modules. Once again, I highly recommend backups and version control if you will be using this feature.
'--== Module1 ==--
'>>> *NextDay(#2/28/2020#)
'2/29/2020
'>>> *NextDay(#2/28/2100#)
'2/29/2100
Private Function NextDay(AsOf As Date) As Date
NextDay = AsOf + 1
End Function
'--== Immediate window ==--
DocTests
Module1: Module1.NextDay(#2/28/2100#) evaluates to: 3/1/2100 Expected: 2/29/2100
Tests passed: 1 of 2
Running a Subset of DocTests
The doc tests should run quickly, even across a large project. However, sometimes we're only interested in the doc tests for a single module. That's often the case when we are adding and testing a new routine. To support this use case, I added an optional parameter to the DocTests
function. That parameter is the module name pattern. By default, it is the asterisk (*
), meaning it will match every module name (I use VBA's Like
operator to perform the comparison).
'--== Immediate Window ==--
DocTests
Tests passed: 249 of 249
DocTests "Module1"
Tests passed: 3 of 3
Image by fernando zhiminaicela from Pixabay