原文:http://www.codeproject.com/Articles/42354/The-Art-of-Logging#testing
.....
A commonly observed paradigm is the following:
try { ComponentX.DoStuff(); } catch (Exception e) { log.Error("ComponentX threw an exception", e); }
Every single time an exception is caught, an ERROR level message is sent to the log. The problem with this is that, quite often, exceptions are anticipated or even expected. With the definition given above, ERROR level messages are intended for those where the user experience is affected or degraded in some way. When catching an exception, think twice before logging it as an ERROR ...
Large applications are typically composed of numerous components of great complexity. As mentioned in the previous section, good messages are brief and provide the most important context information. With components often performing similar tasks such as authentication or connecting to databases, it can sometimes be a tricky task to determine exactly which component wrote a specific message to the log. This is where the concept of named loggers becomes very useful. A logger, when given a name, will write its name to the log each time it is used to submit a message. But, what name should we use?
Our application code already has a naming system which is used to organise it, that of class name and namespace. A common logging paradigm makes use of this existing organisation, by associating a logger with each class that logs information via its name:
namespace Acme.Components.Things { class WidgetClass { static Logger logger = LoggerService.GetLogger("Acme.Components.Things.WidgetClass"); // other class members go here } }
Or alternatively, you can remove the need for a string literal by using the FullName
of the class:
namespace Acme.Components.Things { class WidgetClass { static Logger logger = LoggerService.GetLogger(typeof(WidgetClass).FullName); // other class members go here } }
Any messages logged by this class' logger instance will bear the name Acme.Components.Things.WidgetClass
, allowing them to be unambiguously associated with this class.
public class CommunicationLogic { /// <summary> /// The <see cref="ILogger"> used to log messages /// </see> /// </summary> private static ILogger logger = LoggerService.GetLogger(typeof(CommunicationLogic).FullName); /// <summary> /// A service used by this class /// </summary> private IRemoteService remoteService; public CommunicationLogic(IRemoteService remoteService) { this.remoteService = remoteService; } public void SendMessage(string message) { try { remoteService.SendMessage(message); } catch (Exception e) { logger.WarnFormat(e, "Failed to send the given message [{0}]", message); } } }
The above class has a dependency on some other component, IRemoteService
, and a single method,SendMessage
, which we wish to test. The implementation of this method is trivial; it simply delegates toIRemoteService
in order to send the message. A positive test, where we create a stub of this dependent service, might look like this:
[TestMethod] public void SendMessage_RemoteServiceOK_MessageSent() { // create our test fixture StubbedRemoteService service = new StubbedRemoteService(); CommunicationLogic comLogic = new CommunicationLogic(service); // run the test string message = "Hello World!"; comLogic.SendMessage(message); // verify that the given serivce was used to send the message Assert.AreEqual(message, service.SentMessage); } /// summary /// A stub IRemoteService for test purposes /// summary private class StubbedRemoteService : IRemoteService { public string SentMessage = null; public void SendMessage(string message) { SentMessage = message; } }
Here, we provide a 'stubbed' implementation of the service which simply records the message being sent. Hence this is a true unit test.
Now, we wish to verify that if the remote service should throw an exception, resulting in our message being unsent that this failure is logged. In order to do this, we first extend our stub implementation to force a failure:
private class StubbedRemoteService : IRemoteService { public bool FailOnSend = false; public string SentMessage = null; public void SendMessage(string message) { SentMessage = message; if (FailOnSend) { throw new Exception("Message send failed"); } } }
Then, we simply configure SLF to supply a TestLogger
to our class under test. We are now able to verify that the failure was logged:
[TestMethod] public void SendMessage_RemoteServiceFails_FailureLogged() { // ensure that we record logged messages by supplying a test logger // because our class under creates a static logger, we have to 'inject' our // test logger. TestLogger logger = new TestLogger(); typeof(CommunicationLogic).GetField("logger", BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, logger); // create our test fixture StubbedRemoteService service = new StubbedRemoteService() { FailOnSend = true }; CommunicationLogic comLogic = new CommunicationLogic(service); // run the test string message = "Hello World!"; comLogic.SendMessage(message); // verify that the given serivce was used to send the message Assert.AreEqual(message, service.SentMessage); // verify that the exception was logged Assert.AreEqual(1, logger.LoggedItems.Count); Assert.AreEqual(LogLevel.Warn, logger.LoggedItems[0].LogLevel); Assert.IsNotNull(logger.LoggedItems[0].Exception); }
Simple! Note that whilst we could have unit tested the message that was logged, this would have tightly coupled the unit test to the implementation. What really matters is that the message was logged at the given level and that the exception was included.
PS:http://msdn.microsoft.com/en-us/library/microsoft.build.framework.ilogger.aspxILogger Interface(.NET Framework 4.5)