Skip Navigation LinksHome > View Post

Unit testing a logging wrapper

I've recently been spending lots of time helping a client improve their internal logging capability. In particular we spent time working on wrapping their logging framework of choice (the Enterprise Library or EntLib).

Most developers tend to wrap their logging framework for a number of reasons:

  • leaves a route to change to the logging framework du jour
  • provides an easier to use API for their particular requirements
  • allows the embedding of 'extra' functionality
Whether you think this is a good idea or not (I do) we needed a way to *unit* test the wrappers.

It's widely considered a bad idea to mock classes that you don't own. ADO.NET is perhaps the most widely cited example but I felt the same about EntLib - particularly as most of our conatct with EntLib logging was through static members.

Fortunately there was a relatively easy solution to hand. As with most logging frameworks, EntLib is extensible and allows you to write your own trace listeners. So all we had to do was write a MockTraceListener that would be the sink for trace data that we could use to verify that out logging wrapper was behaving in the correct way.

Here's are the two most important members of the MockTraceListener:

public readonly static IList<LogEntry> LogEntries = new List<LogEntry>();

public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
{
    LogEntry logEntry = data as LogEntry;
    if (logEntry != null)
    {
        LogEntries.Add(logEntry);
    }
    // Snipped for brevity ...
}

As you can see, the guts of this idea is that our MockTraceListener writes the LogEvents to a publicly available static list.

We can then use the contents of this list in our Test's assertions to confirm the behaviour of our code.

[TestMethod]
public void TestBasicLog()
{
    BasicLogger logger = new BasicLogger(typeof(BasicLoggerTests));
    logger.Log("some message", "some category");

    // now that we've logged, we need to check that the wrapper did it's job
    // by accessing the static LogEntries

    Assert.AreEqual(1, MockTraceListener.LogEntries.Count, "The LogEntries count should be 1");
    Assert.AreEqual("some message", MockTraceListener.LogEntries[0].Message, "The Message should be 'some message'");
    Assert.IsTrue(MockTraceListener.LogEntries[0].Categories.Contains("some category"));
    Assert.IsTrue(MockTraceListener.LogEntries[0].Categories.Contains(typeof(BasicLoggerTests).FullName));
}

Config

The part of this that will upset the purists is that, aside from this clearly not being a true *unit* test, our test suite will also require a config file to wire up the MockTraceListener in EntLib. It may not be pure, but I think this is a valid pragmatic approach in this case. Fortunately, Visual Studio Team System's testing supports deploying config files for your tests.

The config file should be called AssemblyName.dll.config (where AssemblyName is the name of the assembly that contains your tests - in the sample code it's TheJoyOfCode.Examples.SampleLoggingWrapper.VstsTests.dll.config).

Finally you have to instruct Visual Studio Team System to deploy the file. There are a number of ways of doing this but the easiest is to specify the file in the deployment section of your testrunconfig file. Click Add File and browse to your new configuration file.

Testrunconfig Deployment

Easy peasy. Why not check out the full source for this (very limited) example: Download Sample Code.

Tags: Other

 
Josh Post By Josh Twist
2:26 AM
28 Mar 2007

» Next Post: Using Enterprise Library to break up your configuration files
« Previous Post: theJoyOfCode Team Reunite

Comments are closed for this post.

Posted by Brian @ 10 Jul 2008 9:05 PM
Josh, I got to ask, because you probably would be able to convince me otherwise. I'm a die-hard opposite - 'don't wrap things like your logging framework into some abstract set of classes that you could easily swap out' - but I respect your opinions and experience a lot, so I'm hoping you can shoot something my way that might convince me otherwise. And maybe this could help someone else out who struggles with that decision too...

My position: - in the many years I've been doing this I've never come in one day and said...we've been using log4net for the past 5 years, let's switch to EntLib. Possible reasons could be 'log4net' went belly up, 'EntLib' has a cool new feature (if it's so cool how have I survived without it up until now? Won't it be in log4net soon if it's that cool?).

Does this mean I object the idea of abstracting it out, for certain 'no' - I respect you greater knowledge too much for that, if you believe in it, there's probably a good reason. Does this mean I won't use EntLib on a different client? Heck no, but am I going to direct everyone to learn a new framework in the lifetime of an application - not unless there's something catastrophic; and to be honest, a Find and Replace with a good RegEx can quickly swap out calls from log4net to EntLib or whatever if we decide to use something different in the next generation of the app or in the middle of the project if absolutely necessary. For me it's that whole Fowler thing with speculative generalities.

Like I said, I deeply respect your opinion and this is why I'm asking for the reason why you support this level of abstraction; I'm not 'closed minded' about it - just up until now the arguments I've heard have been very weak and I haven't seen proof as to it saving the amount of money spent abstracting it and training on the abstraction.

Thanks

Posted by Josh @ 15 Jul 2008 2:23 AM
Hi Brian,

"I've never come in one day and said...we've been using log4net for the past 5 years, let's switch to EntLib" - I think this is a valid point and the possibility of changing logging framework is arguably not enough to warrant wrapping it. However, in defense of this argument I do know a large enterprise that is glad they wrapped their use of EntLib because they've now decided to use log4net instead.

However, there are other reasons:
- the wrapper can help protect you from breaking changes in the logging libraries (for example, EntLib 2 was very different to EntLib 1 - a wrapper might have allowed you to move to EntLib 2 more easily and take advantage of its new features)
- you can provide a different API that is more tailored to the specific requirements of the *application*
- think 15 is too many logging levels? then wrap away!
- provides an opportunity to extend the logging framework (e.g. I used an early version of nlog and wanted to provide support for logging the System.Diagnostics.Trace.CorrelationManager.ActivityId and wanted to build this in seemlessly. A wrapper was the best place to do it so the consumer of the logging API didn't have to worry about it. I've also used the wrapper to provide support for Start and Stop operations in frameworks that don't support this concept - including providing support for the using syntax in c#).

Of course, as with all things, your own mileage may vary. Personally, I've found writing these wrappers to be very easy and inexpensive (and easy to test :)).

Good luck!

Posted by Brian @ 15 Jul 2008 5:04 PM
Okay...so yes, I agree, you need something to 'dumb down' the calls, no doubt about that - I prefer a static class though to a full blow assembly that's wrapping another - low overhead (1 less reference to maintain) - class would have maybe 4 or 5 helper methods, 60-100 lines tops plus GOOD comments - I unit test the functionality of the *helper* methods (using good old fashioned regex to parse the log file written) and trust that the people at P&P (or log4net) tested the actual logging themselves (i.e. I can trust that an EventLogger writes to the EventLog, that an email logger sends an email, I'm only interested in does my helper method do it's part, which using the flat file log I can test).

The 15 levels - yeah too many - the 10 as I said makes since, but again, I'd wrap it in the 4 or 5 helper methods in the static utility class living in the Common.dll if it was really a problem.

So I know, I know, extensibility/scalability - class isn't as easily extended/scaled into multiple projects as a DLL is - but in a shop with rarely new projects, and mostly maintnance, that's an acceptable loss; and maybe that's where our differences lie. And truthfully in it's in our 'Common' which is common utility methods/rules for the who org, so if done right, it's still somewhat extensible/scalable.

But here's my point...if putting something in a wrapper is such a good idea: if I downloaded the logging work you're doing now (I forget the name of the project sorry...it's on my todo list of downloads) and wrapped it, would I not just be inferring that you shouldn't of given me a concrete class to use to begin with because it isn't good enough for my needs and you should of just left me with an abstract class or a plain interface? If I had that many problems with 'missing methods' couldn't I just inherit and extend? Or just use extension methods? If you're predicting that to be the case shouldn't *you* provide the interface - similiar to the IDbConnection in System.Data - Microsoft implemented it's own interface of the concrete classes they provide allowing you to either extend the class through inheritance or create your own where you could then use the interface to make your class usuable by other core framework objects. But alas, yes, that would require all open source frameworkds to settle on a standard interface...so yeah, that's no good.

Where I come from, down here in the weeds of a small IT shop, I work with people who take things at literal and don't infer there own thought process threads - so we end up with applications that have 220 interfaces, one for each manager, one for each DAO - why, not for easy of swapping out, not for any other purpose of someone said it would make the quality of there code increase (which it didn't, in fact it made things go south). Also, it's not for testing if you were thinking that direction - which is another bad idea in my mind - adding complexity for testing sake - seems kinds of backwards (and why I'm struggling with Rhino Mocks)

Anyway, I can definitely see this as a 'it depends' argument, and I can see the point your making, but for it to get me, you'd have to give me more than 1 enterprise who was glad they wrapped it in the many many years you've been doing this, and you'd have to tell me that they built the wrapper, unit tested it, debugged it, have performed maintenance, added features over the past however many years, and swapped it out faster and cheaper than it took for me to do the same with a helper class and update my four or five methods in a helper class to point to a new logging framework that the rest of the application is calling.

But good discussion - I like and the whole 'own mileage may vary' - I think that says it all.

Thanks for discussing with me - gives me another 'hmmm let me think about the possiblities' option to consider on design decisions or answers to client questions down the road. Good stuff. As always I appreciate your time and thanks again.

Posted by Josh @ 16 Jul 2008 12:38 AM
Hi Brian,

You make a lot of points here so I've picked some out and tackle them in isolation.

>> "I prefer a static class though to a full blow assembly that's wrapping another"
I'm not sure I see the difference. You don't need to put a wrapper in a separate assembly, it's fine to do this in your 'common' or 'essentials' library. If you want to use statics, then cool. I prefer not to (so I can Dependency Inject my logger) but this, again, is personal preference.

>> "unit test the functionality of the *helper* methods (using good old fashioned regex to parse the log file written)"
I would avoid writing unit tests that write and read from the file system wherever possible, hence why I advocate the approach suggested in this blog post. I agree that you shouldn't test the EventListener etc provided by P&P.

>> "If I had that many problems with 'missing methods' couldn't I just inherit and extend?"
Sure, and extension methods are great for this sort of thing (provided you can use .NET 3.5) but what if you want to *hide* certain methods provided by your logger of choice? Maybe you don't want your devs to use Trace.Transfer? Maybe you want to reduce the number of logging levels from 15 to 10?

>> "shouldn't *you* provide the interface?"
We could never know what you want for your particular application. If you're totally happy with what you get then you've probably made your decision.

>> "you'd have to give me more than 1 enterprise who was glad they wrapped it in the many many years you've been doing this"
I'm not that old you know :). Actually, every customer and company I've ever worked with that has wrapped their logging framework has been happy they did so. Even though only two I know of decided to change their logging framework - for the reasons cited in my reply above.

>> "I like and the whole 'own mileage may vary'"
Clearly you’ve given this some thought and you feel this approach won't work for you. I'm more than happy with that. Now go and download Ukadc.Diagnostics and enjoy being 'dependency free' when it comes to logging (you just use System.Diagnostics that comes OOB with .NET and configure in our listeners) ;)

Posted by Brian @ 16 Jul 2008 6:03 PM
Ah, this is where there's probably been some confusion and mistakes on my part...whenever I see 'wrap' I assume (obviously incorrectly) 'new assembly', so I'll give you this one.
>>"You don't need to put a wrapper in a separate assembly, it's fine to do this in your 'common' or 'essentials' library."

>> "I would avoid writing unit tests that write and read from the file system wherever possible, hence why I advocate the approach suggested in this blog post. I agree that you shouldn't test the EventListener etc provided by P&P."
I can see that. But it's an edge case - situational I guess.

Here's where we'll have to agree to disagree (with a *but* afterwards, so first - devils advocate -
>> "what if you want to *hide* certain methods provided by your logger of choice? Maybe you don't want your devs to use Trace.Transfer? Maybe you want to reduce the number of logging levels from 15 to 10?"
To me it's like if I can have one assembly that references the actually logger, and everything else references the common assembly where the helper is...they have *NO* access to the actual logging assemebly, regardless whether I want to hide something or not, plus it's one less reference in the other assemblies in the solution - they'll have to create an update to the logging assembly to reference it directly - they *have* to use the helper. If I use a wrapped assembly, and every assembly in the app references (there's no 'gateway') then Joe Jr. Developer over here might in a code behind decide to do some verbose logging by using reflection against the assembly, Mike Mediocre Developer over here might make a utility class for the wrapper in the BLL (which he's responsible for) but he's the only who uses it, and you see where I'm going with this. Yeah...code reviews and coding standards would put a stop to that quick...but a lot of places I've worked look on those things as a waste of precious resources.

Having said that we'd have to agree to disagree I have to throw in this *but* to cover my rear...But then we're back to what you were saying about wrapping != seperate assembly - and there again, that's where I can see conceding to your point because we're both saying the same thing (essentially) at that point - but I'm sure I'm not the only one that incorrectly makes that assumption - so my mistake will be a good lesson for them to learn from.

Regardless, yeah I think in a more sophisticated environment I'd agree with you on *most* of what you've said - if I agreed 100% I'd be a 'yes man' and so I'd have to disagree some on principal alone :) . My hope though is that other people out there can read this friendly debate and get the hard-wiring between there ears working on how it best solves the problem for them in their situation.

Oh, and I'm working on the download...got to figure out this darn Rhino Mock stuff first...so non-intuitive! If you ever feel froggy enough to do a series on that, you'll be my hero (because the stuff out on the site isn't worth much to someone whose never worked with a mock framework before - and torrent to get the demo's? UGH! But at the same time I struggle on whether I really need it for this particular upcoming project or not...but I'll wait until you blog on that subject before I bore you to death. :)

Posted by Josh @ 17 Jul 2008 12:04 AM
OK - last point. In your comment you asked: "If I had that many problems with 'missing methods' couldn't I just inherit and extend?" to which I replied "... but what if you want to *hide* certain methods ...". You last replied to this: "...they have *NO* access to the actual logging assembly...".

This wouldn't be true if you just inherit from their logging class. You can't hide their methods you can only extend the class. And they'd have to add a reference in each project where the subclass is used.
To avoid this, you'd have to wrap (not necessarily in a whole new assembly though).

And I think we're talking about the same thing now :)

Hmm, a series on Rhino Mocks? Maybe - I do use it (we use it in the tests inside Ukadc.Diagnostics) and I have to say, it's one of the least intuitive APIs I've ever come across. Maybe a couple of posts would be a good idea. I'll add it to the list.

Posted by software development company @ 19 Aug 2009 12:48 PM
Quite inspiring,

Anyway, thanks for the post

© 2005 - 2014 Josh Twist - All Rights Reserved.