Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/visual-studio-2008/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 调用静态方法的单元测试方法的模式或实践_C#_Unit Testing_Function_Static_Mocking - Fatal编程技术网

C# 调用静态方法的单元测试方法的模式或实践

C# 调用静态方法的单元测试方法的模式或实践,c#,unit-testing,function,static,mocking,C#,Unit Testing,Function,Static,Mocking,最近,我一直在认真思考“模拟”从我试图测试的类调用的静态方法的最佳方法。以以下代码为例: using (FileStream fStream = File.Create(@"C:\test.txt")) { string text = MyUtilities.GetFormattedText("hello world"); MyUtilities.WriteTextToFile(text, fStream); } 我知道这是一个相当糟糕的示例,但它有三个静态方法调用,它们都略有

最近,我一直在认真思考“模拟”从我试图测试的类调用的静态方法的最佳方法。以以下代码为例:

using (FileStream fStream = File.Create(@"C:\test.txt"))
{
    string text = MyUtilities.GetFormattedText("hello world");
    MyUtilities.WriteTextToFile(text, fStream);
}
我知道这是一个相当糟糕的示例,但它有三个静态方法调用,它们都略有不同。File.Create函数访问文件系统,我不拥有该函数。MyUtilities.GetFormattedText是我拥有的一个函数,它完全是无状态的。最后,MyUtilities.WriteTextToFile是我拥有的一个函数,它访问文件系统

我最近一直在思考的是,如果这是遗留代码,我如何重构它以使其更易于单元测试。我听过一些关于不应该使用静态函数的论点,因为它们很难测试。我不同意这个观点,因为静态函数是有用的,我不认为仅仅因为正在使用的测试框架不能很好地处理它,就应该放弃一个有用的工具

经过大量的搜索和思考,我得出结论,基本上有4种模式或实践可以用来使调用静态函数的函数单元可测试。这些措施包括:

public class MyInstanceClass
{
    private Action<string, FileStream> writeFunction = delegate { };

    public MyInstanceClass(Action<string, FileStream> functionDependency)
    {
        writeFunction = functionDependency;
    }

    public void DoSomething2()
    {
        using (FileStream fStream = File.Create(@"C:\test.txt"))
        {
            string text = MyUtilities.GetFormattedText("hello world");
            writeFunction(text, fStream);
        }
    }
}
  • 不要模仿静态函数,只让单元测试调用它
  • 将静态方法包装在一个实例类中,该实例类实现了一个带有所需函数的接口,然后使用依赖项注入在类中使用它。我将把它称为接口依赖注入
  • 使用Moles(或TypeMock)劫持函数调用
  • 对函数使用依赖注入。我将把它称为函数依赖项注入
  • 我听过很多关于前三种实践的讨论,但是当我在思考这个问题的解决方案时,我想到了第四个想法,即函数依赖注入。这类似于在接口后面隐藏静态函数,但实际上不需要创建接口和包装器类。这方面的一个例子如下:

    public class MyInstanceClass
    {
        private Action<string, FileStream> writeFunction = delegate { };
    
        public MyInstanceClass(Action<string, FileStream> functionDependency)
        {
            writeFunction = functionDependency;
        }
    
        public void DoSomething2()
        {
            using (FileStream fStream = File.Create(@"C:\test.txt"))
            {
                string text = MyUtilities.GetFormattedText("hello world");
                writeFunction(text, fStream);
            }
        }
    }
    
    公共类MyInstanceClass
    {
    私有操作writeFunction=delegate{};
    公共MyInstanceClass(操作函数依赖项)
    {
    writeFunction=functionDependency;
    }
    公共无效DoSomething2()
    {
    使用(FileStream fStream=File.Create(@“C:\test.txt”))
    {
    string text=MyUtilities.GetFormattedText(“hello world”);
    writeFunction(文本、流媒体);
    }
    }
    }
    
    有时,为静态函数调用创建接口和包装器类可能会很麻烦,而且它可能会污染您的解决方案,因为许多小型类的唯一目的是调用静态函数。我完全支持编写易于测试的代码,但这种做法似乎是一种解决糟糕测试框架的方法

    当我思考这些不同的解决方案时,我了解到上面提到的所有4种实践都可以应用于不同的情况。以下是我所想的应用上述实践的正确立场

  • 如果静态函数是纯无状态的,并且不访问系统资源(如文件系统或数据库),则不要模拟它。当然,可以提出这样的论点:如果正在访问系统资源,那么不管怎样,这都会将状态引入到静态函数中
  • 当您正在使用的多个静态函数在逻辑上都可以添加到单个接口时,请使用接口依赖项注入。这里的关键是使用了几个静态函数。我认为在大多数情况下,情况并非如此。一个函数中可能只调用一个或两个静态函数
  • 在模拟外部库(如UI库或数据库库(如linq到sql))时,请使用MOLE。我的观点是,如果Moles(或TypeMock)被用来劫持CLR以模拟您自己的代码,那么这表明需要进行一些重构来解耦对象
  • 当正在测试的代码中存在少量静态函数调用时,请使用函数依赖项注入。在大多数情况下,为了测试在我自己的实用程序类中调用静态函数的函数,我倾向于使用这种模式

  • 这些都是我的想法,但我真的很想得到一些反馈。在调用外部静态函数时,测试代码的最佳方法是什么?

    选择#1是最佳方法。不要模仿,只要使用静态方法就可以了。这是最简单的路线,完全符合您的需要。您的两种“注入”方案仍在调用静态方法,因此您无法通过所有额外的包装获得任何好处。

    只需为静态方法创建一个单元测试,并在方法内部随意调用它即可进行测试,而无需对其进行模拟。

    使用依赖项注入(选项2或4)这绝对是我攻击它的首选方法。它不仅使测试更容易,而且有助于分离关注点,防止类膨胀

    我需要澄清的是,静态方法很难测试,这不是真的。静态方法在另一种方法中使用时会出现问题。这使得调用静态方法的方法很难测试,因为静态方法无法模拟。通常的例子是I/O。在您的例子中,您将文本写入文件(WriteTextToFile)。如果在这种方法中出现故障怎么办?由于该方法是静态的,无法模拟,因此您不能按需创建失败案例等案例。如果创建接口,则可以模拟对WriteTextToFile的调用,并使其模拟错误。是的,您将有更多的接口和类
    public void WriteMyFile(IFileRepository aRepository){
        try{
            using (FileStream fStream = aRepository.Create(@"C:\test.txt")){
                string text = MyUtilities.GetFormattedText("hello world");
                aRepository.WriteTextToFile(text, fStream);
            }
        }
        catch(Exception e){
            //You can now mock Create or WriteTextToFile and have it throw an exception to test this code.
        }
    }
    
    public int GetNewSalary(int aRaiseAmount){
        //Do you really want the test of this method to fail because the database couldn't be queried?
        int oldSalary = DBUtilities.GetSalary(); 
        return oldSalary + aRaiseAmount;
    }
    
    public int GetNewSalary(IDBRepository aRepository,int aRaiseAmount){
        //This call can now be mocked to always return something.
        int oldSalary = aRepository.GetSalary();
        return oldSalary + aRaiseAmount;
    }