【Testability】怎样整合log4net不影响代码的可测试性?

根据DDIY(Don't Do It Yourself)原则,如果程序需要日志功能,log4net是一个很好的选择。

但在整合log4net的过程中,我们该怎么做不影响代码的可测试性。


以下通过一个简单的例子来探讨一下这个问题。


此示例会用到以下开源库


方式一:static readonly

此种方式类不会暴露ILog接口给外部,如果不需要模拟ILog时,这样的方式其实也挺好的。

(注:我们不会引入一个中间层来抽象log4net的调用。因为我觉得log4net就是比较好的选择。软件切换到其他日志库的可能性几乎为零。)

Calculator1.cs
using log4net;

namespace T01Log4net
{
    public class Calculator1
    {
        private static readonly ILog Logger = LogManager.GetLogger(typeof(Calculator1));

        public int Add(int addend1, int addend2)
        {
            Logger.Info($"Adding {addend1} and {addend2}...");
            var result = addend1 + addend2;
            Logger.Info($"Sum is {result}");
            return result;
        }
    }
}
Calculator1Tests.cs
using NUnit.Framework;

namespace T01Log4net.Tests
{
    [TestFixture]
    public class Calculator1Tests
    {
        [TestCase(0, 0, 0)]
        [TestCase(-1, 1, 0)]
        [TestCase(-1, -2, -3)]
        [TestCase(1, 2, 3)]
        public void Add_TwoNumbers_ShouldReturnExpectedSum(int addend1, int addend2, int expected)
        {
            var calculator1 = new Calculator1();

            var actual = calculator1.Add(addend1, addend2);

            Assert.That(actual, Is.EqualTo(expected));
        }
    }
}

方式二:constructor injection

这种方式的好处是我们可以测试ILog的行为。但怎样让每个类注入一个和自己类型相关的ILog呢?

Autofac提供了一种简单的实现方式。我们待会看看怎么实现。

Calculator2.cs
using System;
using log4net;

namespace T01Log4net
{
    public class Calculator2
    {
        private readonly ILog _logger;

        public Calculator2(ILog logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public int Add(int addend1, int addend2)
        {
            _logger.Info($"Adding {addend1} and {addend2}...");
            var result = addend1 + addend2;
            _logger.Info($"Sum is {result}");
            return result;
        }
    }
}
Calculator2Tests.cs
using log4net;
using Moq;
using NUnit.Framework;


namespace T01Log4net.Tests
{
    [TestFixture]
    public class Calculator2Tests
    {
        private Mock<ILog> _loggerMock;
        private Calculator2 _calculator2;


        [SetUp]
        public void SetUp()
        {
            _loggerMock = new Mock<ILog>();
            _calculator2 = new Calculator2(_loggerMock.Object);
        }


        [TestCase(0, 0, 0)]
        [TestCase(-1, 1, 0)]
        [TestCase(-1, -2, -3)]
        [TestCase(1, 2, 3)]
        public void Add_TwoNumbers_ShouldReturnExpectedSum(int addend1, int addend2, int expected)
        {
            var actual = _calculator2.Add(addend1, addend2);


            Assert.That(actual, Is.EqualTo(expected));
        }


        [Test]
        public void Add_TwoNumbers_ShouldLogThemAndSum()
        {
            _calculator2.Add(1, 2);


            _loggerMock.Verify(l => l.Info("Adding 1 and 2..."), Times.Once);
            _loggerMock.Verify(l => l.Info("Sum is 3"), Times.Once);
        }
    }
}

我们一般不会测试ILog。如果测试ILog的行为,应该属于“过度测试”的一种。以上示例只是演示这种可能性。


App中的整合

Bootstrapper.cs
using Autofac;
using Autofac.log4net;

namespace T01Log4net.ConsoleApp
{
    public class Bootstrapper
    {
        public IContainer Bootstrap()
        {
            var builder = new ContainerBuilder();

            builder.RegisterType<Calculator1>();

            builder.RegisterType<Calculator2>();

            var loggingModule = new Log4NetModule
            {
                ConfigFileName = "log4net.config",
                ShouldWatchConfiguration = true
            };
            builder.RegisterModule(loggingModule);

            return builder.Build();
        }
    }
}

log4net.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
  </configSections>

  <log4net>

    <root>
      <level value="ALL" />
      <appender-ref ref="allAppender" />
      <appender-ref ref="warnAppender" />
    </root>

    <appender name="allAppender" type="log4net.Appender.FileAppender">
      <appendToFile value="false" />
      <encoding value="utf-8"/>
      <file value="log\all.log" />
      <immediateFlush value="false" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{HH:mm:ss,fff} [%2thread] %-5level %-20.20logger{1} - %message%newline" />
      </layout>
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <threshold value="ALL" />
    </appender>

    <appender name="warnAppender" type="log4net.Appender.FileAppender">
      <appendToFile value="false" />
      <encoding value="utf-8"/>
      <file value="log\warn.log" />
      <immediateFlush value="false" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{HH:mm:ss,fff} [%2thread] %-5level %-20.20logger{1} - %message%newline" />
      </layout>
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <threshold value="WARN" />
    </appender>

  </log4net>

</configuration>

Program.cs
using Autofac;

namespace T01Log4net.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var container = GetContainer())
            {
                var calculator1 = container.Resolve<Calculator1>();
                calculator1.Add(1, 2);

                var calculator2 = container.Resolve<Calculator2>();
                calculator2.Add(3, 4);
            }
        }

        private static IContainer GetContainer()
        {
            var bootstrapper = new Bootstrapper();
            return bootstrapper.Bootstrap();
        }
    }
}


以下是产生的日志信息





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值