patternMinor
LogManager Tests
Viewed 0 times
logmanagertestsstackoverflow
Problem
I should have started with this code. I wrote my logging API without writing unit tests, and since I recently wrote an automagic unit testing API I though I might as well go ahead and use it.
So I wrote a
MockLogger class module
This logger doesn't actually log anything - instead, it raises a
```
Option Explicit
Private Type TMockLogger
Name As String
MinLevel As LogLevel
Formatter As ILogMessageFormatter
End Type
Private this As TMockLogger
Public Event Logging(ByVal loggerName As String, ByVal level As LogLevel, ByVal message As String)
Implements ILogger
Public Function Create(ByVal loggerName As String, ByVal loggerMinLevel As LogLevel, Optional ByVal logFormatter As ILogMessageFormatter = Nothing)
If logFormatter Is Nothing Then
Set logFormatter = DefaultLogMessageFormatter.Instance
End If
Dim result As New MockLogger
Set result.Formatter = logFormatter
result.MinLevel = loggerMinLevel
result.Name = loggerName
Set Create = result
End Function
Friend Property Get Name() As String
Name = this.Name
End Property
Friend Property Let Name(ByVal value As String)
this.Name = value
End Property
Friend Property Get MinLevel() As LogLevel
MinLevel = this.MinLevel
End Property
Friend Property Let MinLevel(ByVal value As LogLevel)
this.MinLevel = value
End Property
Friend Property Get Formatter() As ILogMessageFormatter
Set Formatter = this.Formatter
End Property
Friend Property Set Formatter(ByVal value As ILogMessageFormatter)
Set this.Formatter = value
End Property
Private Property Get ILogger_Formatter() As ILogMessageFormatter
Set ILogge
So I wrote a
MockLogger implementation of the ILogger interface, still with a default instance (like all other ILogger implementations in the library), but I kept it private, so client code can't see/use it.MockLogger class module
This logger doesn't actually log anything - instead, it raises a
Logging event that the test code can listen to and evaluate the logger name, log level and message being passed to it by the LogManager:```
Option Explicit
Private Type TMockLogger
Name As String
MinLevel As LogLevel
Formatter As ILogMessageFormatter
End Type
Private this As TMockLogger
Public Event Logging(ByVal loggerName As String, ByVal level As LogLevel, ByVal message As String)
Implements ILogger
Public Function Create(ByVal loggerName As String, ByVal loggerMinLevel As LogLevel, Optional ByVal logFormatter As ILogMessageFormatter = Nothing)
If logFormatter Is Nothing Then
Set logFormatter = DefaultLogMessageFormatter.Instance
End If
Dim result As New MockLogger
Set result.Formatter = logFormatter
result.MinLevel = loggerMinLevel
result.Name = loggerName
Set Create = result
End Function
Friend Property Get Name() As String
Name = this.Name
End Property
Friend Property Let Name(ByVal value As String)
this.Name = value
End Property
Friend Property Get MinLevel() As LogLevel
MinLevel = this.MinLevel
End Property
Friend Property Let MinLevel(ByVal value As LogLevel)
this.MinLevel = value
End Property
Friend Property Get Formatter() As ILogMessageFormatter
Set Formatter = this.Formatter
End Property
Friend Property Set Formatter(ByVal value As ILogMessageFormatter)
Set this.Formatter = value
End Property
Private Property Get ILogger_Formatter() As ILogMessageFormatter
Set ILogge
Solution
Public Sub TestCannotRegisterLoggerTwice()
On Error GoTo ExpectedError
Dim logger As ILogger
Set logger = MockLogger.Create("TestLogger", TraceLevel)
LogManager.Register logger
LogManager.Register logger
Assert.Fail "Expected error was not raised."
CleanExit:
Exit Sub
ExpectedError:
Assert.AreEqual LogManagerError.DuplicateLoggerError, Err.Number
Err.Clear
Resume CleanExit
End SubThat's a rather ugly way of making a test expect an error. How about this instead:
'@ExpectedError(1098)
Public Sub TestCannotRegisterLoggerTwice()
Dim logger As ILogger
Set logger = MockLogger.Create("TestLogger", TraceLevel)
LogManager.Register logger
LogManager.Register logger
End SubThis requires a number of changes in the
UnitTesting and Reflection libraries, to recognize the ExpectedErrorAttribute and make attributes support parameters. These libraries are outside the scope of this review, but require the SUT LogManager class to raise an application-defined or object-defined error instead, as shown on Stack Overflow - adding vbObjectError to the error number causes the testing library to only see an error #440 - Automation error, which prevents these automagic "attributes" from working properly.So this:
Public Enum LogManagerError
DuplicateLoggerError = vbObjectError + 1098
LoggerNotRegisteredError
End EnumBecomes this:
Public Enum LogManagerError
DuplicateLoggerError = 1098
LoggerNotRegisteredError
End EnumAnd now the tests that expect an error can be greatly simplified:
'@ExpectedError(1098)
Public Sub TestCannotRegisterLoggerTwice()
Dim logger As ILogger
Set logger = MockLogger.Create("TestLogger", TraceLevel)
LogManager.Register logger
LogManager.Register logger
End Sub
'@ExpectedError(1099)
Public Sub TestMustRegisterLogger()
If LogManager.IsEnabled(TraceLevel) Then
Assert.Inconclusive "Trace level was enabled before test started."
End If
LogManager.Log TraceLevel, "This shouldn't be working.", "TestLogger"
End SubThis also requires the
TestEngine to no longer treat tests without assertions as inconclusive, as recommended here - such tests should simply pass.A test that expects an error should succeed if the specified error is raised, fail if it isn't, and be inconclusive if an unexpected error is raised.
Actually, since these automagic comments/attributes are so fantastic, why not use them, and drop this annoying
Test prefix to all test method names, which are already verbose enough as is?'@TestMethod
'@ExpectedError(1098)
Public Sub CannotRegisterLoggerTwice()
Dim logger As ILogger
Set logger = MockLogger.Create("TestLogger", TraceLevel)
LogManager.Register logger
LogManager.Register logger
End Sub
'@TestMethod
'@ExpectedError(1099)
Public Sub MustRegisterLogger()
If LogManager.IsEnabled(TraceLevel) Then
Assert.Inconclusive "Trace level was enabled before test started."
End If
LogManager.Log TraceLevel, "This shouldn't be working.", "TestLogger"
End SubNote that the order of the attributes isn't important, but it's best to have them in the same order in every method, for consistency.
Code Snippets
Public Sub TestCannotRegisterLoggerTwice()
On Error GoTo ExpectedError
Dim logger As ILogger
Set logger = MockLogger.Create("TestLogger", TraceLevel)
LogManager.Register logger
LogManager.Register logger
Assert.Fail "Expected error was not raised."
CleanExit:
Exit Sub
ExpectedError:
Assert.AreEqual LogManagerError.DuplicateLoggerError, Err.Number
Err.Clear
Resume CleanExit
End Sub'@ExpectedError(1098)
Public Sub TestCannotRegisterLoggerTwice()
Dim logger As ILogger
Set logger = MockLogger.Create("TestLogger", TraceLevel)
LogManager.Register logger
LogManager.Register logger
End SubPublic Enum LogManagerError
DuplicateLoggerError = vbObjectError + 1098
LoggerNotRegisteredError
End EnumPublic Enum LogManagerError
DuplicateLoggerError = 1098
LoggerNotRegisteredError
End Enum'@ExpectedError(1098)
Public Sub TestCannotRegisterLoggerTwice()
Dim logger As ILogger
Set logger = MockLogger.Create("TestLogger", TraceLevel)
LogManager.Register logger
LogManager.Register logger
End Sub
'@ExpectedError(1099)
Public Sub TestMustRegisterLogger()
If LogManager.IsEnabled(TraceLevel) Then
Assert.Inconclusive "Trace level was enabled before test started."
End If
LogManager.Log TraceLevel, "This shouldn't be working.", "TestLogger"
End SubContext
StackExchange Code Review Q#64214, answer score: 5
Revisions (0)
No revisions yet.