HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

LogManager Tests

Submitted by: @import:stackexchange-codereview··
0
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 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 Sub


That'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 Sub


This 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 Enum


Becomes this:

Public Enum LogManagerError
    DuplicateLoggerError = 1098
    LoggerNotRegisteredError
End Enum


And 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 Sub


This 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 Sub


Note 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 Sub
Public Enum LogManagerError
    DuplicateLoggerError = vbObjectError + 1098
    LoggerNotRegisteredError
End Enum
Public 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 Sub

Context

StackExchange Code Review Q#64214, answer score: 5

Revisions (0)

No revisions yet.