【问题标题】:How would I refactor out a static method so I can test my method?我将如何重构一个静态方法以便我可以测试我的方法?
【发布时间】:2013-06-21 23:37:47
【问题描述】:

我知道我不能使用 Moq 在我的测试方法中模拟出静态方法调用,那么我需要做什么来重构该方法以便我可以测试它?我也有一个调用基类方法的方法,我需要重构它吗?如果需要怎么做?我不想使用 MS.Fakes 或 TypeMocks 来创建 shim,我宁愿重构并编写可靠的代码!

    public override DateTime ResolveDate(ISeries comparisonSeries, DateTime targetDate)
    {
        if (comparisonSeries == null)
        {
            throw new ArgumentNullException("comparisonSeries");
        }

        switch (comparisonSeries.Key)
        {
            case SeriesKey.R1:
            case SeriesKey.R2:
            case SeriesKey.R3:
            case SeriesKey.R4:
            case SeriesKey.R5:
                return DateHelper.PreviousOrCurrentQuarterEnd(targetDate);
        }

        return base.ResolveDate(comparisonSeries, targetDate);
    }

    [TestMethod]
    public void SomeTestMethod()
    {
        var mockIAppCache = new Mock<IAppCache>();
        var mockISeries = new Mock<ISeries>();

        ReportFR2 report = new ReportFR2(SeriesKey.FR2, mockIAppCache);
        DateTime resolvedDate = report.ResolveDate(mockISeries, DateTime.Now);

        //Assert.AreEqual("something", "something");

    }

【问题讨论】:

  • 或者我想在我的被测方法中调用静态方法?我认为答案是否定的,因为单元测试只测试被测方法中的逻辑,而不是其他任何东西,除非它是私有方法。如果我错了,请有人纠正我!
  • 用接口包装 DateHelper 并将您的 IDateHelper 作为包含类中的方法参数或作为构造函数参数注入,就像您对 IAppCache 所做的那样?
  • 什么会更好? 1)用接口包装 DateHelper 并将其传入或 2)在类中的“受保护的内部虚拟”方法中隔离静态方法,然后我可以模拟出该方法。
  • 取决于实现。如果 DateHelper 有一些您想在测试场景中使用的方法,只需在您直接调用的方法中设置 Now ,虚拟方法可能是一个不错的选择。不过,我会在界面方面犯错。
  • 如果我在接口一侧“出错”,我将不得不将我的类从静态类重构为实例类,对吧?还是有别的办法?

标签: c# unit-testing moq moq-3


【解决方案1】:

看了以上,你可以测试三个基本条件:

  1. 当比较系列为空时

  2. 比较序列键为 R1:R5 时

  3. 当比较系列键不为空且除 R1 之外的任何内容时:R5

在条件 1 中,您可以很容易地通过测试来解决这个问题。

在条件 2 中,当它是 R1:R5 时,它出现在你的静态方法中。

  • 从您的 ResolveDate 方法的角度来看,您仍然关心到达此分支时的值。
  • 如果能够证明 R1:R5 调用了静态帮助程序,那就太好了,但正如 cmets 中所述,唯一干净的方法是使用接口包装 DateHelper 并将其传递给这节课。这样做可能不值得。
  • 如果您决定不这样做,我建议您仍然提供属于该分支的测试,然后还编写另一组直接针对您的 DateHelper.PreviousOrCurrentQuarterEnd() 函数的测试,触及所有边缘情况。如果出现故障,这将有助于找出哪些代码是罪魁祸首。

条件 3 与条件 2 的情况相同。虽然它在您的基类中,但它仍然是一个有效的逻辑分支。

  • 同样,您将很难证明它调用了您的基类,

  • 但检查结果仍然有效。

所以,我认为您可以编写四组测试开始,然后在通过这些测试之后,您可以决定是否要重构您的实用程序类DateHelper.我猜你会说不:-D

  1. 给定一个 ReportRF2 类和一个空的比较系列

    • 调用resolveDate时

    • 它应该抛出一个空引用异常。使用
      `Assert.Throws( () => report.ResolveDate(null, DateTime.Now ));

  2. 给定一个 ReportRF2 类和一组 R1:R5 中的系列键

    • 在解析边界 X 的日期时(例如 1/1/0001),它应该等于 y;

    • 当“..”代表...,...时; (重复您的边缘/边界情况;考虑使用数据驱动)

  3. 给定 ReportRF2 类和一个序列键不在 R1:R5 和 NOT NULL 集合中

    • 在解析边界 X 的日期时,...类似于 #2,但预期结果可能不同。
  4. 给定静态实用程序类 DateHelper

    • 在计算 PreviousOrCurrentQuarterEnd() 并且日期为 X 时,它应该等于 y,
    • 类似于上面 #2 中的边缘情况。

这将为您提供预期结果,并告诉您失败源于您的ResolveDate 方法或DateHelper.PreviousOrCurrentQuarterEnd() 方法。它可能不像纯粹主义者想要的那样隔离,但只要你涵盖了你的边缘情况和你的快乐路径,它就证明你的应用程序正在按计划运行(只要这些测试通过了)。

除了比较系列为空时,它实际上不允许您断言采取了特定行为,因此由您决定是否需要该验证。但是,您仍然应该有证据证明当某些值或范围进入时,您会获得可预测的输出,这会增加一些价值。

【讨论】:

    【解决方案2】:

    只是为了补充@Damon 的好答案,用接口包装DateHelper 可以很容易地完成:

    public interface IDateHelper
    {
        DateTime PreviousOrCurrentQuarterEnd(DateTime targetDate);
    }
    

    如前所述,您需要一个实现此接口的实例类,但仅限于您的生产代码,因为单元测试将只使用Mock&lt;IDateHelper

    public class InstanceDateHelper : IDateHelper
    {
        public DateTime PreviousOrCurrentQuarterEnd(DateTime targetDate)
        {
            return DateTimeHelper.PreviousOrCurrentQuarterEnd(targetDate);
        }
    }
    

    Voilà,您现在可以模拟 IDateHelper 接口,并且您有一个使用现有静态代码的实现。

    我已经使用这种包装技术对启动新Process 的方法编写单元测试,因此当我只需要知道该方法是否在test 会调用.Start(StartInfo),没有副作用。

    图片这个方法:

    public bool StartProcessAndWaitForExit(ProcessStartInfo info)
    {
        var process = Process.Start(info); // test-hindering static method call
        //...
    }
    

    我必须做的唯一改变是:

    public bool StartProcessAndWaitForExit(IProcessWrapper process, ProcessStartInfo info)
    {
        var process = process.Start(info); // injected wrapper interface makes method testable
        //...
    }
    

    如果ResolveDate 是您的类中唯一需要IDateHelper 的方法,则将其作为方法参数注入即可;如果你有一堆方法都需要它,那么将它作为构造函数参数注入并创建一个private readonly IDateHelper _helper; 字段(在构造函数中初始化)是最好的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-07-31
      • 1970-01-01
      • 2010-11-28
      • 2018-02-04
      • 2016-03-08
      • 2011-11-05
      • 1970-01-01
      • 2015-12-05
      相关资源
      最近更新 更多