单元测试-如何使用MOQ框架在c#中模拟静态方法?

我最近一直在进行单元测试,并且已经使用MOQ框架和MS Test成功地模拟了各种场景。 我知道我们无法测试私有方法,但我想知道是否可以使用MOQ模拟静态方法。

Himanshu asked 2020-01-24T10:15:31Z
4个解决方案
93 votes

Moq(和其他基于DynamicProxy的模拟框架)无法模拟任何非虚拟或抽象方法的东西。

只能使用基于Profiler API的工具来伪造密封/静态类/方法,例如Typemock(商业)或Microsoft Moles(免费,在Visual Studio 2012 Ultimate / 2013/2015中称为Fakes)。

或者,您可以重构设计以抽象化对静态方法的调用,然后通过依赖注入将这种抽象提供给您的类。 然后,您不仅会有更好的设计,而且可以通过Moq等免费工具进行测试。

无需完全使用任何工具就可以应用允许可测试性的通用模式。 请考虑以下方法:

public class MyClass
{
    public string[] GetMyData(string fileName)
    {
        string[] data = FileUtil.ReadDataFromFile(fileName);
        return data;
    }
}

您可以尝试将其包装到TestableMyClass方法中,而不是尝试模拟MyClass,如下所示:

public class MyClass
{
    public string[] GetMyData(string fileName)
    {
        string[] data = GetDataFromFile(fileName);
        return data;
    }

    protected virtual string[] GetDataFromFile(string fileName)
    {
        return FileUtil.ReadDataFromFile(fileName);
    }
}

然后,在单元测试中,派生自MyClass,并将其命名为TestableMyClass。然后,您可以重写GetDataFromFile方法以返回您自己的测试数据。

希望能有所帮助。

Igal Tabachnik answered 2020-01-24T10:16:09Z
42 votes

将静态方法转换为静态Func或Action的另一种选择。 例如。

原始代码:

    class Math
    {
        public static int Add(int x, int y)
        {
            return x + y;
        }

您想“模拟” Add方法,但是不能。 将上面的代码更改为此:

        public static Func<int, int, int> Add = (x, y) =>
        {
            return x + y;
        };

现有的客户端代码不必更改(可以重新编译),但是源代码保持不变。

现在,从单元测试中,要更改方法的行为,只需为其重新分配一个内联函数:

    [TestMethod]
    public static void MyTest()
    {
        Math.Add = (x, y) =>
        {
            return 11;
        };

根据您要执行的操作,将所需的任何逻辑放入方法中,或仅返回一些硬编码值。

不一定每次都可以执行此操作,但是在实践中,我发现此技术很好用。

[编辑]我建议您将以下清除代码添加到单元测试类中:

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Math).TypeInitializer.Invoke(null, null);
    }

为每个静态类添加单独的一行。 这是在单元测试完成运行之后,它将所有静态字段重置为其原始值。 这样,同一项目中的其他单元测试将以正确的默认值开始,而不是模拟版本。

zumalifeguard answered 2020-01-24T10:17:05Z
7 votes

如其他答案所述,MOQ无法模拟静态方法,通常,应尽可能避免使用静态方法。

有时这是不可能的。 一种是使用旧版或第三方代码,甚至使用静态的BCL方法。

一种可能的解决方案是将静态函数包装在具有可模拟接口的代理中

    public interface IFileProxy {
        void Delete(string path);
    }

    public class FileProxy : IFileProxy {
        public void Delete(string path) {
            System.IO.File.Delete(path);
        }
    }

    public class MyClass {

        private IFileProxy _fileProxy;

        public MyClass(IFileProxy fileProxy) {
            _fileProxy = fileProxy;
        }

        public void DoSomethingAndDeleteFile(string path) {
            // Do Something with file
            // ...
            // Delete
            System.IO.File.Delete(path);
        }

        public void DoSomethingAndDeleteFileUsingProxy(string path) {
            // Do Something with file
            // ...
            // Delete
            _fileProxy.Delete(path);

        }
    }

缺点是,如果有很多代理,那么ctor可能会变得很混乱(尽管可以说,如果有很多代理,那么该类可能会尝试做太多事情并且可以重构)

另一种可能性是拥有一个“静态代理”,其背后的接口实现方式不同

   public static class FileServices {

        static FileServices() {
            Reset();
        }

        internal static IFileProxy FileProxy { private get; set; }

        public static void Reset(){
           FileProxy = new FileProxy();
        }

        public static void Delete(string path) {
            FileProxy.Delete(path);
        }

    }

现在我们的方法变成

    public void DoSomethingAndDeleteFileUsingStaticProxy(string path) {
            // Do Something with file
            // ...
            // Delete
            FileServices.Delete(path);

    }

为了进行测试,我们可以将FileProxy属性设置为模拟。 使用这种样式可以减少要注入的接口的数量,但可以使依赖关系变得不那么明显(尽管不如我想象的原始静态调用那么多)。

AlanT answered 2020-01-24T10:17:52Z
6 votes

Moq无法模拟类的静态成员。

在设计可测性代码时,避免使用静态成员(和单例)非常重要。 可以帮助您重构代码以实现可测试性的设计模式是依赖注入。

这意味着要更改此:

public class Foo
{
    public Foo()
    {
        Bar = new Bar();
    }
}

public Foo(IBar bar)
{
    Bar = bar;
}

这使您可以使用单元测试中的模拟。 在生产中,您可以使用像Ninject或Unity这样的依赖注入工具将所有内容连接在一起。

我前一段时间写了一个关于这个的博客。 它说明了哪些模式可用于更好的可测试代码。 也许可以帮助您:单元测试,地狱还是天堂?

另一个解决方案可能是使用Microsoft Fakes Framework。 这不能代替编写好的设计的可测试代码,但可以为您提供帮助。 Fakes框架允许您模拟静态成员,并在运行时使用您自己的自定义行为替换它们。

Wouter de Kort answered 2020-01-24T10:18:39Z
translate from https://stackoverflow.com:/questions/12580015/how-to-mock-static-methods-in-c-sharp-using-moq-framework