【问题标题】:How to test logic which is dependent on current date [duplicate]如何测试依赖于当前日期的逻辑[重复]
【发布时间】:2012-09-13 20:21:42
【问题描述】:

我有这种方法,它取决于当前日期。它检查今天是周日、周一、周二还是周三,然后给出 5 天的交货时间来运送物品。如果是周四、周五或周六,那么它会提供 6 天的提前期来考虑周末。

private DateTime GetEstimatedArrivalDate()
{
    DateTime estimatedDate; 
    if (DateTime.Now.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = DateTime.Now.Date.AddDays(6);
    }
    else
    {
        estimatedDate = DateTime.Now.Date.AddDays(5);
    }
    return estimatedDate; 
}

实际的估计逻辑更复杂。为了这个问题,我已经简化了它。我的问题是如何为这样的事情编写单元测试,这取决于今天的日期?

【问题讨论】:

  • 答案(几乎)在问题“我如何为这样的事情编写单元测试,这取决于今天的日期?”重构使用依赖注入的方法,如 Mark 的回答。

标签: c# unit-testing


【解决方案1】:

您需要将当前日期作为参数传入:

private DateTime GetEstimatedArrivalDate(DateTime currentDate)
{
    DateTime estimatedDate; 
    if (currentDate.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = currentDate.AddDays(6);
    }
    else
    {
        estimatedDate = currentDate.AddDays(5);
    }
    return estimatedDate; 
}

在实际代码中,您可以这样称呼它:

DateTime estimatedDate = GetEstimatedArrivalDate(DateTime.Now.Date);

那么你可以如下测试:

DateTime actual = GetEstimatedArrivalDate(new DateTime(2010, 2, 10));
DateTime expected = ...;
// etc...

请注意,这还修复了程序中的一个潜在错误,即在连续调用 DateTime.Now 之间日期会发生变化。

【讨论】:

  • 我刚刚发布了相同的答案。你打败了我的速度:)
  • 感谢您指出错误。我喜欢你的解决方案。但请注意,此方法是私有的。我当然可以将其公开以进行测试,但随后我失去了我的良好封装。我想我可以接受,因为这是一个足够小的函数。
【解决方案2】:

一般来说,你会想在一个接口后面抽象出获取当前日期和时间的方法,例如:

public interface IDateTimeProvider
{
    DateTime Now { get; }
}

真正的服务是:

public class DateTimeProvider: IDateTimeProvider
{
    public DateTime Now
    {
        get
        {
            return DateTime.Now;
        }
    }
}

测试服务是:

public class TestDateTimeProvider: IDateTimeProvider
{
    private DateTime timeToProvide;
    public TestDateTimeProvider(DateTime timeToProvide)
    {
        this.timeToProvide = timeToProvide;
    }

    public DateTime Now
    {
        get
        {
            return timeToProvide;
        }
    }
}

对于需要当前时间的服务,让它们将 IDateTimeProvider 作为依赖项。对于真实的东西,传递一个新的 DateTimeProvider();当你是一个组件时,传入一个新的 TestDateTimeProvider(timeToTestFor)。

【讨论】:

  • 我会给这个+1,因为你绝对正确,但同时我觉得它只是为了测试一个琐碎的方法有点过头了。不过,我很佩服您对报道的奉献精神,如果有很多方法或需要测试的某些复杂方法,我可能会采用此方法:)
  • 现在可能微不足道,但事情很容易改变。无论如何,+1 因为我肯定会围绕 DateTime 类制作一个适配器以启用测试。另外,对于 TDDer,这将是正常的路线。
  • 附带说明,使用模拟框架会比创建一个您现在还需要维护的“测试服务”更好吗?好像有点无趣?
  • 埃里克感谢您的回答。但我同意保罗的观点。这对我来说可能有点开销。
  • 不用担心。 =) @David - 也许是这样,但是当手动模拟它是一件简单的事情时,我倾向于远离模拟框架。至于维护,应该不会比在 IOC 容器中添加映射并向需要它的类添加构造函数参数更困难。然而,我现在只是在扮演魔鬼的拥护者。如果您只有几个这样的方法,Mark 的解决方案可能是最好的 - 但是,不止几个,并且传递当前日期时间会变得乏味。
【解决方案3】:

让您的类采用IClock 参数(通过构造函数或属性)

interface IClock
{
    DateTime Now { get; }
}

然后您可以使用假实现进行测试

class FakeClock : IClock
{
    DateTime Now { get; set }
}

其余时间都是真正的实现。

class SystemClock : IClock
{
    DateTime Now { get { return DateTime.Now; } }
}

【讨论】:

    【解决方案4】:

    我建议以Mark suggests 的形式执行此操作,但添加了一个用于生产用途的重载调用,该调用不带参数并使用 DateTime.Now

    private DateTime GetEstimatedArrivalDate()
    {
        return GetEstimatedArrivalDate(DateTime.Now);
    }
    
    private DateTime GetEstimatedArrivalDate(DateTime currentDate)
    {
        DateTime estimatedDate; 
        if (currentDate.DayOfWeek >= DayOfWeek.Thursday)
        {
            estimatedDate = currentDate.AddDays(6);
        }
        else
        {
            estimatedDate = currentDate.AddDays(5);
        }
        return estimatedDate; 
    }
    

    【讨论】:

      【解决方案5】:

      这样做的一种“常见”方式是“伪造”当前系统日期(可以通过多种方式完成),然后在“已知”日期测试您的代码。

      另一种有趣的方法是将您的实现稍微更改为:

      private DateTime GetEstimatedArrivalDate()
      {
          return GetEstimatedArrivalDate(DateTime.Now);
      }
      
      private DateTime GetEstimatedArrivalDate(DateTime forDate)
      {
          DateTime estimatedDate; 
          if (forDate.DayOfWeek >= DayOfWeek.Thursday)
          {
              estimatedDate = forDate.Date.AddDays(6);
          }
          else
          {
              estimatedDate = forDate.Date.AddDays(5);
          }
          return estimatedDate; 
      }
      

      然后使用带有参数的方法来测试“立即”日期。

      【讨论】:

        【解决方案6】:

        似乎有足够数量的案例,您可以明确地测试它们。该方法取决于今天的日期,但输出仅取决于星期几,并且每个日期都有星期几。

        【讨论】:

          【解决方案7】:

          您可以在正常执行期间传入一个返回 DateTime.Now 的委托,然后在您的测试中传入另一个返回固定日期的委托,并基于此断言您的结果。

          【讨论】:

            【解决方案8】:

            我会给出有争议的答案,不要测试它。

            逻辑是微不足道的,它具有零依赖性,我相信良好的代码覆盖率,但当它增加复杂性而没有真正的收益时则不然。

            【讨论】:

            • 这听起来很合理,但这正是我希望意外改变的那种方法。如果公司改变他们的运输政策,开始提供分层服务,或者更换运营商。现在从技术上讲,一旦它变得复杂,你就可以开始测试它,但我倾向于认为这是应用程序中的一个重要枢纽,它应该处于测试状态,以便绝对每个人都知道它发生变化的那一刻。以防万一。
            • 呸。如果我们决定在一周内交换周四和周五的头寸怎么办?你永远不会知道这段代码被破坏,直到它太晚了!如果我们决定开始计算天数向后,那么,如果你不能给我一个闪烁的红色“测试失败”灯,我什至不知道要告诉你什么。 ;)
            • 我同意其他评论者的观点。这种方法中的逻辑实际上是不平凡的。我将其简化只是为了将其作为我的问题的示例。我确实想彻底测试一下。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-09-27
            • 2013-03-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-23
            相关资源
            最近更新 更多