views:

444

answers:

10

I am writing a class to help me unit test my code. It looks like this:

/// <summary>
/// Wrapper for the LogManager class to allow us to stub the logger
/// </summary>
public class Logger
{
    private static ILogger _logger = null;

    /// <summary>
    /// This should be called to get a valid logger.
    /// </summary>
    /// <returns>A valid logger to log issues to file.</returns>
    public static ILogger GetLogger()
    {
        if (_logger == null)
          _logger = LogManager.GetLogger("logger");

        return _logger;
    }

    /// <summary>
    /// This is used by unit tests to allow a stub to be used as a logger.
    /// </summary>
    /// <param name="logger"></param>
    /// <returns></returns>
    public static ILogger GetLogger(ILogger logger)
    {
        _logger = logger;
        return _logger;
    }
}

The second method is for unit testing only. I never intend to have it called in my production code.

Is this bad practice? Should I find another way that does not do this?

+10  A: 

In my opinion yes, this is a bad practice. Unit testing is meant to test implementation of your code, not really to influence it. In some cases I've found it practical to organize my code/methods in a manner to make it more easily/thoroughly tested, but writing code in a class being tested for specific use in testing is a different story.

Chris Forrette
Test-driven design means that your implementation is heavily influenced by your tests. Note, I am not saying this is a particularly good example of test-driven design.
flq
I totally agree with Chris. [Moles](http://research.microsoft.com/en-us/projects/moles/) is a library that allows you to "inject" code when testing. This helps prevent "designing to the tests" instead of "designing to the requirements".
Stephen Cleary
A: 

Any time you have code in a file/library/whatever that is not intended to be used in a production capacity it is just code bloat. Your production objects should not be coded to support your unit tests, your unit tests should be implemented to support your production objects.

Joel Etherton
+2  A: 

You could consider this:

/// <summary>
/// Wrapper for the LogManager class to allow us to stub the logger
/// </summary>
public class Logger
{
    public static ILogger _logger = null;

    /// <summary>
    /// This should be called to get a valid logger.
    /// </summary>
    /// <returns>A valid logger to log issues to file.</returns>
    public static ILogger Logger
    {
        get
        {
            if (_logger == null)
            {
                _logger = LogManager.GetLogger("logger");
            }
            return _logger
        }
        set
        {
            _logger = value;
        }
    }
}

It sets the logger the first time the getter is called if it's not already set using the setter.

Jeroen
@Jeroen, take a look at the Singleton pattern. There are some threading issues here. You'll need to add a lock and an object to lock on.
Lucas B
"There are some threading issues here. You'll need to add a lock and an object to lock on." - not necessarily, it depends on the implementation. A race condition can result in LogManager.GetLogger being called twice, but depending on implementation this may or may not be a problem.
Joe
@Joe - You are right. In this case, `LogManager.GetLogger()` manages the "singleton" activity its self. Having GetLogger called more than once does not really matter.
Vaccano
A: 

I wouldn't say this particular piece of code is a problem as such. As long as you state in your xmldoc comments that the method(s) are intended for unit testing only, no (sane!) person would use it in production code.

Though, when possible, try to keep such things in your unit testing library. As Joel Etherton states, it is code bloat, in any case...

Zor
+7  A: 

Normally, code that exists only to facilitate unit testing hides a design flaw.

In this case, the flaw you're hiding is the use of global state. Classes that use your logger are secretly dependent on it: you can't tell they need a logger unless you read their source code!

Those classes should require an ILogger in their constructors, making it obvious what they need to work and making them easy to test.

Jeff Sternal
While I agree with this in general, isn't it a bit overkill to make this a constructor parameter? Loggers are very specific in purpose, and really shouldn't be exposed outside the class encapsulation, don't you think?
jvenema
@jvenema - if a class is able to use a logger, I think it ought to advertise that. All the more so if it *requires* a logger. (Though if the logger is optional, I don't object to exposing it via property injection.)
Jeff Sternal
@jvenema - You could provide two constructors: one that takes an ILogger, and one that doesn't take a parameter but just gets a default logger from a factory or whatever and calls the first constructor. This can sort of hide (in code, not in Intellisense) the dependency that Jeff mentioned, but at least the code is a little cleaner.
Ed Schwehm
@jvenema no, that's proper use of a constructor. Every service or object which your class requires to function properly, including a logger, should always be provided via the constructor.
Rex M
@all - I'd lean towards Jeff's comment. To me, if I'm working with a "Foo" object's API, and I'm checking through the constructor overloads to use it, I don't give a crap about logging 95% of the time, so keeping it out of the way makes much more sense to me. IMHO, logging should be a feature that you never find by accident; it's not a feature of the API, it's a tool for when you're tracking problems.
jvenema
+1  A: 

IMO, yes, this is bad code. Two reasons:

  • Logger knows about LogManager. That tightly couples Logger to LogManager, and presents the problem you encounter; you have to use LogManager to get an ILogger object, and cannot stub or mock LogManager.
  • You have added code to a production class for the sole purpose of unit testing. That will confuse developers down the road, who see the test hook and think they can use it in production code.

I would recommend one or more of the following:

  • Define an interface ILogManager that exposes GetLogger(), and implement it on LogManager. Then, create a property on Logger of type ILogManager (or require one to be passed in when instantiating a Logger) and give Logger a LogManager from outside the scope of Logger (through an IoC framework, or simply from whatever instantiates Logger). This is loose coupling; you can now supply any class that implements ILogManager including MockLogManager, and Logger doesn't know the difference.
  • For purposes of your unit test, derive a "proxy" from Logger and implement any test-only methods there. I use this method mainly to unit test protected methods of a class, but if you find yourself unable to decouple LogManager from Logger, you can at least hide this derived proxy and its hooks in your test assemblies, where production code shouldn't be able to reference them.
KeithS
+1  A: 

Yes, the design is not perfect :( GetLogger method actually sets the logger! Use setter method - this looks more suitable.

BTW, what do you test? If you use .NET standard loggers - you can add or delete listeners (as I remember) and if you don't need tests to write something somewhere - configure apropriate log listeners.

And have seen the Moq library? http://code.google.com/p/moq/ I liked it for mocking.

dmitko
.NET Loggers have some limitations. For example, the set of listeners is shared between Debug and Trace loggers, so if you wanted to write more information to a Debug log than to the Trace log you were using for basic logging, or to a different log file, you'd have to reimplement your basic logging not to use Trace. You also have to explicitly call the loggers in code; third-party loggers include some aspect-oriented designs that can be utilized via decorating methods and fields you want to log.
KeithS
+1  A: 

I don't have any strange constructors. Instead I have a private variable in the View:

private readonly ILog _log = LogManager.GetLogger(typeof(MyViewClassName));

Which can then be used as such, in this case it is called inside the catch block:

_log.Error("SomePage.aspx.cs Page_Load failed", ex);

There is no wrapper for log4net. ILog is part of log4net. I'm curious as to why you aren't using Mocks also. That would make sense and then you wouldn't need to do what you are doing.

hyprsleepy
+1  A: 

To answer the general question of "Is it bad to add code just for unit testing?", no it's not bad in general. Testability is an important feature of a system because it helps guarantee the correctness of the system (through the actual tests) and it is highly correlated with other good design tenets, like loose coupling and high cohesion.

Look at it another way; if you have two similar ways of building some code, and the main difference is testability, then go for the more testable option.

But as for the specific case of your code, I agree with most of the other posts here; there is a better way to write that code.

Ed Schwehm
A: 

What's wrong here is that the code that gets the logger has to know what logger it wants. In fact, it has no reason to care.

That's why this is a poster child for inversion of control. At the very simplest, stick the name of the logger in a config file and use a different value in your test code. At the more sophisticated level, use something like MEF to decide which logger to load.

Steven Sudit