包装静态类/方法以进行单元测试?

时间:2016-03-06 14:03:38

标签: c# .net unit-testing nunit moq

我有一个我用于记录的静态类:

public static class myLogger
{
    public static ErrorLogging(string input)
    {
        //dostuff
    }
}

我使用它的方式是:

public class myClassthatDoesStuff
{
    ...
    myLogger.ErrorLogging("some error ocurred");
    ...
}

如何对 myLogger 类进行moq以便对其进行单元测试并确保执行 ErrorLogging 方法? 是否可以在构造函数中没有设置任何参数(构造函数注入)的情况下执行此操作? myClassthatDoesStuff 要求构造函数中没有参数。

5 个答案:

答案 0 :(得分:6)

This blog post描述了完全相同的情况 - 您有一个旧的静态日志记录方法,并希望在可测试的代码中使用它。

  • 将静态类包装在非静态类中 - 不仅用于测试,还用于一般用途。
  • 将新的非静态类的方法解压缩到接口中。
  • 无论您在哪里依赖静态类,都要依赖于接口。例如,如果类DoesSomething需要静态类中的函数,请执行以下操作:

    public interface ILogger
    {
        void ErrorLogging(string input);
    }
    
    public class MyClassthatDoesStuff
    {
        private readonly ILogger _logger;
    
        public MyClassthatDoesStuff(ILogger logger)
        {
            _logger = logger;
        }
    }
    

这给你带来两个好处:

  1. 您可以对旧的静态类进行单元测试(假设它没有状态且不依赖于任何状态的任何状态)(尽管如果是这种情况,我认为无论如何都可以进行单元测试。)
  2. 您可以对将使用该静态类的代码进行单元测试(通过删除对该静态类的直接依赖性。)您可以将ILogger替换为模拟类,例如将错误消息添加到一个清单。

    class StringLogger : List<string>, ILogger
    {
        public void ErrorLogging(string input)
        {
           Add(input);
        }
    }
    
    var testSubject = new MyClassthatDoesStuff(new StringLogger());
    

答案 1 :(得分:4)

如果您无法将其从静态类更改为非静态类,请使用非静态类包装它...

void Test()
{
    string testString = "Added log";
    var logStore = new List<string>();
    ILogger logger = new MyTestableLogger(logStore);

    logger.ErrorLogging(testString);

    Assert.That(logStore.Any(log => log==testString));
}

public interface ILogger
{
    void ErrorLogging(string input);
}

public class MyTestableLogger : ILogger
{
    public MyTestableLogger(ICollection<string> logStore)
    {
        this.logStore = logStore;
    }

    private ICollection<string> logStore;

    public void ErrorLogging(string input)
    {
        logStore.Add(input);
        MyLogger.ErrorLogging(input);
    }
}

public static class MyLogger
{
    public static void ErrorLogging(string input)
    {
        // Persist input string somewhere
    }
}

答案 2 :(得分:1)

您可以使用Microsoft的Shims

来完成此操作

假设您的项目被称为ConsoleApplication1 首先转到单元测试项目引用,右键单击包含myClassthatDoesStuff类的程序集,然后选择“Add Fakes Assembly”。

Add Fakes Assembly

使用填充程序进行单元测试将如下所示:

[TestClass()]
public class MyClassthatDoesStuffTests
{
    [TestMethod()]
    public void ImportansStuffTest()
    {
        using (ShimsContext.Create())
        {
            bool logCalled = false;
            ConsoleApplication1.Fakes.ShimmyLogger.ErrorLoggingString = 
                (message) => logCalled = true;
            new myClassthatDoesStuff().ImportansStuff();
            Assert.IsTrue(logCalled);
        }
    }
}

答案 3 :(得分:1)

您可以使用Typemock Isolator完成此操作。

它允许您避免所有这些数量的包装器和接口,并且这样做很简单:

[TestMethod]
public void TestLogging()
{
    //Arrange
    Isolate.WhenCalled(() => myLogger.ErrorLogging("")).CallOriginal();

    //Act
    var foo = new myClassthatDoesStuff();
    foo.DoStuff();

    //Assert
    Isolate.Verify.WasCalledWithAnyArguments(() => myLogger.ErrorLogging(""));
}

答案 4 :(得分:0)

一种选择是将静态类包装在一个实例中(非静态类)。另一个是依赖,不是依赖于静态方法本身,而是依赖于委托。运行时实现是静态方法,但为了测试,您可以注入匿名或本地方法。

首先,声明一个与您的静态方法具有相同签名的委托:

public delegate void LogErrorMethod(string errorMessage);

然后修改依赖静态方法的类注入委托,改为依赖。这看起来就像注入一个接口或类:

public class MyClassthatDoesStuff
{
    private readonly LogErrorMethod _logErrorMethod;

    public MyClassthatDoesStuff(LogErrorMethod logErrorMethod)
    {
        _logErrorMethod = logErrorMethod;
    }

    public void DoSomethingAndLogError()
    {
        // do something
        _logErrorMethod("An error has occurred");
    }
}

如果您使用的是像 IServiceCollection/IServiceProvider 这样的 IoC 容器,那么您可以告诉它使用静态方法作为委托的实现。

services.AddSingleton<LogErrorMethod>(MyLogger.ErrorLogging);

但是为了测试,您可以提供匿名方法。如果你想要一个什么都不做的日志记录方法,你可以这样做:

LogErrorMethod fakeLoggingMethod = message => { };

或者,如果您想捕获消息以便验证它,您可以这样做:

string loggedMessage = null;
LogErrorMethod fakeLoggingMethod = message => { loggedMessage = message; };