【问题标题】:UnitTest a method that is time-dependentUnitTest 一种与时间相关的方法
【发布时间】:2013-07-03 13:10:41
【问题描述】:

您将如何测试与时间相关的方法?

一个简单的例子可以是:

public static int GetAge(DateTime birthdate)
{
    // ...
}

[TestMethod]
public void GetAgeTest()
{
    // This is only ok for year 2013 !
    Assert.AreEqual(GetAge(new DateTime(2000, 1, 1)), 13);
}

问题在于它仅适用于当前年份(在本例中为 2013 年)。新的一年一开始,我就得重写所有依赖时间的测试......

对这种方法进行单元测试的最佳方法是什么?

【问题讨论】:

标签: c# .net unit-testing


【解决方案1】:

您可以覆盖 GetAge 方法查找今天日期的机制。

如果您将 DateTime 包装在另一个类中,则可以将实际调用替换为在单元测试时返回已知值的假调用。

您可以使用依赖注入在生产的真实实现和测试的假实现之间切换。

【讨论】:

  • 我试过了,效果很好;但是,在我看来,这增加了太多开销。此外,要注入依赖项,我不能使用 static 方法,这对于静态助手来说有点烦人......
  • @Bidou。是的,对于一个相对简单的测试来说,这是一个开销——但对于更复杂的测试来说,它会自己承担。
  • @Bidou,确实增加了一些尴尬。然而,它提出了一个有趣的问题,这就是为什么被测试的对象首先依赖于时间信息? GetAge 方法是否应该真正隐式地依赖 DateTime.Now 或者接受引用 DateTime 作为参数是否更有意义?这里的可测试性困难表明(不必要的?)依赖关系可能会通过稍微不同的设计来消除。
【解决方案2】:

另外,不要忘记“预期”参数是AreEquals 中的第一个参数。

[TestMethod]
public void GetAgeTest()
{
    int age = 13;
    Assert.AreEqual(age, GetAge(DateTime.Now.AddYears(age * -1));
}

最好将测试拆分为 Arrange - Act - Assert 组,如下所示:

[TestMethod]
public void GetAgeTest()
{
    // Arrange
    int age = 13;

    // Act
    int result = GetAge(DateTime.Now.AddYears(age * -1);

    // Assert
    Assert.AreEqual(age, result);
}

【讨论】:

  • 与克劳迪奥的回答一样;罕见的竞争条件可能导致此测试失败。这是直接依赖于当前时间的测试方法的一个根本问题。
  • @DanBryant 即使运气好到跨年夜自动化测试套件失败了一次,我认为无论如何也没有人会在工作中看到它哈哈
【解决方案3】:

两个主要选项:

  1. 创建一个隔离时间相关功能的服务,并将其接口注入到需要它的类中。然后你可以模拟这个接口来获得你需要的测试行为。

  2. 使用像Moles 这样的迂回测试框架(也有其他一些),可以重新路由像DateTime.Now 这样的静态方法调用。

【讨论】:

【解决方案4】:

这样的事情就可以解决问题

var birthDate = new DateTime(DateTime.Now.Year - 13, 1, 1)
int age = GetAge(birthDate);
Assert.AreEqual(age, 13);

【讨论】:

  • 如果时间正好在换年时进行,则在新年前夜执行此测试可能会失败。
  • @Dan Bryant:这是一个非常极端的情况...... 100.000.000 分之一...... 或 1000.000.000.000 分之一......我个人不会担心,因为这几乎是不可能的会发生,但这是个人意见
  • @Dan:很好看!你可以通过喝一杯香槟来避免这个问题,而不是在关键时刻运行你的单元测试;-)
  • @Claudio,这可能并不像您想象的那么罕见;许多自动构建服务器被配置为在深夜运行,并且它们通常不休假。这也是一个故意简化的例子。在较长时间内发生的更复杂的操作可能会出现更复杂的故障情况,例如闰年效应、夏令时效应等。这些极端情况通常需要测试,如果没有完全控制程序的时间测量。
  • @DanBryant:我知道 CI 服务器会自动运行此代码,即使在这种情况下,机会也是千分之一。感谢您的反馈,但在我看来,您在这个特殊情况下想多了。
【解决方案5】:

另一个解决方案是使用 Microsoft Shimes and Fakes。 看http://msdn.microsoft.com/en-us/library/hh549175.aspx

using (ShimsContext.Create())
{
    // Shim DateTime.Now to return a fixed date:
    System.Fakes.ShimDateTime.NowGet = () =>
    { 
        return new DateTime(fixedYear, 1, 1); 
    };
}

【讨论】:

    【解决方案6】:

    在这种特定情况下可能不是最佳选择,但您也可以将函数的接口扩展为还需要传递 DateTime now

    public static int GetAge(DateTime birthdate, DateTime now)
    {
        // ...
    }
    
    [TestMethod]
    public void GetAgeTest()
    {
        Assert.AreEqual(GetAge(new DateTime(2000, 1, 1), new DateTime(2013, 1, 1)), 13);
    }
    

    您基本上是将获取当前日期的责任委托给调用者(不过也将单元测试问题推到了那里)。

    一个很好的副作用是您在此函数的范围内确实获得了一个明确定义的“现在”。在刻度级别上,调用 DateTime.Now 的那一刻会有所作为。您可以在抽象层面上将函数描述为“给定这个生日和这个当前日期,年龄是”。

    同样,这可能不是在所有情况下都最好的方法,但我认为在某些情况下值得考虑。

    【讨论】:

      【解决方案7】:

      另一个解决方案是使用 VirtualTime。 ,它简化了单元测试依赖于时间的应用程序,请在此处查看如何使用和示例Unit Testing: DateTime.Now

      【讨论】:

        【解决方案8】:

        您可以将时间视为依赖项并将其注入您的方法中。我建议使用NodaTime 中的IClock 接口。然后在您的单元测试中,您可以使用来自NodaTime.TestingFakeClock 类。

        你修改过的方法。

        public static int GetAge(IClock clock, DateTime birthdate)
        {
            now = clock.GetCurrentInstant().ToDateTimeUtc();
            // use now and birthdate to calculate age
        }
        

        然后你可以这样写你的单元测试。

        [TestMethod]
        public void GetAgeTest()
        {
            var clock = new FakeClock(Instant.FromUtc(2013, 1, 1, 0, 0, 0)); // Create a fake clock that always return 2013/01/01 0:00:00
            var birthDate = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            Assert.AreEqual(GetAge(clock, birthdate), 13); Always return 13
        }
        

        如果您决定将GetAge 移动到它自己的类中。

        public class AClass
        {
            private readonly IClock _clock;
        
            public AClass(IClock clock) => _clock = clock;
        
            public int GetAge(DateTime birthdate)
            {
                now = _clock.GetCurrentInstant().ToDateTimeUtc();
                // use now and birthdate to calculate age
            }
        }
        

        FakeClock 是一个非常强大的测试工具。你可以从我的博文here了解更多。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-12-31
          • 2014-05-19
          • 1970-01-01
          • 2011-05-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多