我如何重构静态方法,以便测试我的方法?

时间:2013-06-21 23:37:47

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

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

    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");

    }

2 个答案:

答案 0 :(得分:2)

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

  1. 当比较系列为空时

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

  3. 当比较系列键不为空且除R1:R5

  4. 之外的任何其他内容

    在条件1中,您可以非常轻松地进行测试。

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

    • 从您的ResolveDate方法的角度来看,您仍然关心它到达此分支时的值。
    • 能够证明R1:R5调用静态帮助器会很好,但正如注释中所提到的,干净利落的唯一方法是将DateHelper与接口包装并将其传递给构造函数这个班。这可能不值得努力。
    • 如果您决定不这样做,我建议您仍然提供属于该分支的测试,然后再编写另一组测试,直接针对您的DateHelper.PreviousOrCurrentQuarterEnd()函数,点击所有边缘情况。如果出现问题,这将有助于确定哪些代码是罪魁祸首。

    条件3作为条件2也可以这样说。虽然它在你的基类中,但它仍然是一个有效的逻辑分支。

    • 同样,你很难证明它叫你的基类,

    • 但检查结果仍然有效。

    所以,我认为你可以编写四套测试来开始,然后在你完成这些传递之后,你可以决定是否要重构你的实用工具类DateHelper.我的猜测是你我会说不:-D

    1. 给定ReportRF2类和null比较系列

      • 调用report.ResolveDate

      • 它应该抛出一个Null引用异常。使用
        `Assert.Throws(()=&gt; 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中的边缘情况。
    5. 然后,这将为您提供预期结果的报道,并告诉您失败源于您的ResolveDate方法或DateHelper.PreviousOrCurrentQuarterEnd()方法。它可能不像纯粹主义者那样孤立,但只要你覆盖你的边缘情况和你快乐的路径,它就证明你的应用程序按计划运行(只要这些测试正在通过)。

      它真正让你做的是断言除了比较系列为空时,采取了特定的行为,因此由你决定是否需要该验证。但是,您仍然应该证明,当某些值或范围进入时,您会得到可预测的输出,并且会增加一些价值。

答案 1 :(得分: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);
    }
}

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

我已经使用这种包装技术在一个启动新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;字段(在构造函数中初始化)是最好的方法。