2

我知道我不能使用 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");

    }
4

2 回答 2

2

综观上述,您可以测试三个基本条件:

  1. 当比较系列为空时

  2. 当比较系列键为 R1:R5 时

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

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

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

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

条件 3 和条件 2 一样。虽然它在您的基类中,但它仍然是一个有效的逻辑分支。

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

  • 但是检查结果仍然有效。

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

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

    • 调用 report.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()方法。它可能不像纯粹主义者想要的那样隔离,但只要你涵盖了你的边缘情况和你的快乐路径,它就证明你的应用程序正在按计划运行(只要这些测试通过了)。

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

于 2013-06-22T03:29:27.523 回答
1

只是为了添加到@Damon 的好答案,DateHelper可以很容易地用接口包装 :

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

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

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

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

我已经使用这种包装技术在启动 new 的方法上编写单元测试Process,因此当我只需要知道被测方法是否会调用时,我可以在没有实际启动完整进程的情况下测试该方法.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;字段(在构造函数中初始化)是最好的方法。

于 2013-06-22T20:55:12.137 回答