gpt4 book ai didi

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

转载 作者:IT王子 更新时间:2023-10-29 04:08:16 26 4
gpt4 key购买 nike

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

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 种模式或做法 可用于使调用静态函数的函数可进行单元测试。这些包括以下内容:
  • 根本不要模拟静态函数,让单元测试调用它。
  • 将静态方法包装在一个实例类中,该类实现了一个带有您需要的函数的接口(interface),然后使用依赖注入(inject)在您的类中使用它。我将其称为接口(interface)依赖注入(inject)。
  • 使用 Moles(或 TypeMock)来劫持函数调用。
  • 对函数使用依赖注入(inject)。我将其称为函数依赖注入(inject)。

  • 关于前三种做法我听过不少讨论,但是当我在思考这个问题的解决方案时,我想到了 的第四个想法。函数依赖注入(inject) .这类似于在接口(interface)后面隐藏静态函数,但实际上不需要创建接口(interface)和包装类。这方面的一个例子如下:
    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);
    }
    }
    }

    有时,为静态函数调用创建接口(interface)和包装类可能很麻烦,并且它可能会用许多小类来污染您​​的解决方案,这些小类的唯一目的是调用静态函数。我完全赞成编写易于测试的代码,但这种做法似乎是一个糟糕的测试框架的解决方法。

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

  • 这些是我的想法,但我真的很感激对此的一些反馈。测试调用外部静态函数的代码的最佳方法是什么?

    最佳答案

    使用依赖注入(inject)(选项 2 或 4)绝对是我解决此问题的首选方法。它不仅使测试更容易,而且有助于分离关注点并防止类变得臃肿。

    我需要澄清的是,静态方法难以测试是不正确的。静态方法在其他方法中使用时会出现问题。这使得调用静态方法的方法难以测试,因为无法模拟静态方法。通常的例子是 I/O。在您的示例中,您将文本写入文件 (WriteTextToFile)。如果在此方法期间某些事情失败了怎么办?由于该方法是静态的并且无法模拟,因此您不能按需创建失败案例等案例。如果您创建一个接口(interface),那么您可以模拟对 WriteTextToFile 的调用并让它模拟错误。是的,您将拥有更多的接口(interface)和类,但通常您可以将类似的功能逻辑地组合在一个类中。

    无依赖注入(inject):
    这几乎是没有被 mock 的选项 1。我不认为这是一个可靠的策略,因为它不允许您进行彻底的测试。

    public void WriteMyFile(){
    try{
    using (FileStream fStream = File.Create(@"C:\test.txt")){
    string text = MyUtilities.GetFormattedText("hello world");
    MyUtilities.WriteTextToFile(text, fStream);
    }
    }
    catch(Exception e){
    //How do you test the code in here?
    }
    }

    依赖注入(inject):
    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.
    }
    }

    另一方面,如果无法读取/写入文件系统/数据库,您是否希望您的业务逻辑测试失败?如果我们在工资计算中测试数学是否正确,我们不希望 IO 错误导致测试失败。

    无依赖注入(inject):

    这是一个有点奇怪的例子/方法,但我只是用它来说明我的观点。
    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;
    }

    依赖注入(inject):
    public int GetNewSalary(IDBRepository aRepository,int aRaiseAmount){
    //This call can now be mocked to always return something.
    int oldSalary = aRepository.GetSalary();
    return oldSalary + aRaiseAmount;
    }

    提高速度是 mock 的额外好处。 IO 成本高昂,减少 IO 将提高测试速度。不必等待数据库事务或文件系统功能将提高您的测试性能。

    我从未使用过 TypeMock,所以我不能谈论它。不过,我的印象与您的相同,如果您必须使用它,那么可能需要进行一些重构。

    关于c# - 调用静态方法的单元测试方法的模式或实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5503702/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com