Want to show your appreciation? Please to my charity.

Saturday, April 25, 2009

Common.Logging for .Net and It's Use in Unit Test

Background of Common.Logging

The Commons Logging was widely adopted by majority of projects in Java community. It is an ultra-thin layer between your code and different logging implementations.

  • A library that uses the commons-logging API can be used with any logging implementation at runtime.
  • An application can also benefit from the ability to change to a different logging system when requirement changes, for example company wide logging policy changes.
  • It doesn't force you to configure so every friendly to unit testing, if you are not testing your logging code, nothing needs to be done.

Two years ago, when I had to decide a .Net logging system for my project, I was lucky to find that netcommon project has Common.Logging library that does exactly the same. Since then it is my favorite logging choice for .Net libraries and applications.

DevIL's Diary has an interesting blog compares Common.Logging and System.Diagnostics Trace. This makes me feel good on the decision I made two yeas ago :)

Why unit test logging code

In addition to make the coverage report looks better, there is actually a few important reasons that I need to test logging code.

  • Logging is like Assertion, it has side effects (about Java and .Net). So I need to test my code both when logging is on and off.
  • I need to ensure some important information, those are warning and above are actually gets logged.
  • I need to test the logic to suspend repeated warnings (see example below).

else if(!_isWrongWrappedReaderTypeWarningGiven && _log.IsWarnEnabled)
{
    _log.Warn(String.Format(
        "Expected original reader to be {0} but got {1}. " + 
        "Note: warning is suspended for subsequent repeated events.", 
        typeof(OracleDataReader).FullName, unwrappedReader.GetType().FullName));
    _isWrongWrappedReaderTypeWarningGiven = true;
}

In memory sink for unit testing

To unit test logging code, I need an in memory sink to store logging events so I can verify against it. I had been using log4net MemoryAppender for a while until recently I believe that my life can be better with something other then MemoryAppender, which:

  • I had to configure log4net and Common.Logging for my unit test
  • My unit test is strong coupled with log4net
  • The unit test code is not at all looked clean

That drove me to wrote the InMemoryLoggerFactoryAdaptor for Common.Logging. It is available in the download area. Example below is a test case of OdpNetDataReaderWrapperTests:

        [Test] public void GiveWarningOnceOnlyWhenNonOdpReaderIsEncountered()
        {
            var factory = (InMemoryLoggerFactoryAdaptor) LogManager.Adapter;
            InMemoryLogger logger = factory.GetInMemoryLogger(typeof (OdpNetDataReaderWrapper));
            logger.Level = LogLevel.Warn;
            _mockery.ReplayAll();
            _testee.WrappedReader = _wrapped;
            _testee.RowsExpected = 2313;
            _testee.RowsExpected = 948;
            var query = from entry in logger.LogEntries
                        where entry.Message.ToString().Contains(
                            "Expected original reader to be " + 
                            typeof (OracleDataReader).FullName)
                        select entry;
            Assert.That(query.Count(), Is.EqualTo(1));
            Assert.That(query.First().LogLevel, Is.EqualTo(LogLevel.Warn));
            _mockery.VerifyAll();
        }

In the real situation, the factory and logger should be initialized in the test setup and log entries should be cleared in the when tear down. But the example here shows how easily I can verify a particular warning was given and only given once with help of Ling (If you are still writing code targeting to .Net 2.0 just like me, check out LinqBridge).

No comments:

Post a Comment