代码之家  ›  专栏  ›  技术社区  ›  Richard Ev

根据今天的日期进行日期处理的单元测试代码

  •  15
  • Richard Ev  · 技术社区  · 16 年前

    当代码根据当前日期处理日期时,测试应该覆盖边缘情况,如闰年以及更频繁的月份和年份边界。

    在我们的代码中,我们总是使用 DateTime.Now (.net,在我们的例子中)。

    如何对此类代码进行单元测试?

    这是哪里 Dependency Injection 变得非常有用?

    编辑

    这是一个稍微的旁白,但显然下一个版本的typemock允许伪造 日期时间

    https://blog.typemock.com/2009/05/mockingfaking-datetimenow-in-unit-tests.html

    9 回复  |  直到 7 年前
        1
  •  15
  •   Amy B    16 年前

    在我们的代码中,我们总是使用datetime.now.net来提取当前日期。如何对此类代码进行单元测试?

    这是一个依赖关系,并且是一个不确定的依赖关系。您需要将代码的责任划分得多一点。

    之前:

    • 有一些代码使用当前的日期时间来执行x。

    后:

    • 应该有一些代码负责获取当前的日期时间。
    • 应该有一些代码使用日期时间来执行x。

    这两组代码不应该相互依赖。

    这种分离依赖关系的模式也适用于其他情况(数据库、文件系统等)。

        2
  •  12
  •   Anton Gogolev    16 年前

    使用DI来注入“当前日期和时间”无疑是一种过度杀伤力。我宁愿重构代码以在任意日期运行。也就是说,我会改变的 function calculateRevenue() function calculateRevenueOn(datetime date) . 如果您需要计算当前日期以外的某些日期,则可以更容易地测试和使用此方法。

        3
  •  10
  •   Davy Landman    16 年前

    我使用了OrenEini(又名AyendeRhien)在他的博客中讨论的非常实用的方法。 Dealing with time in tests .

    有一个这样的静态类:

    public static class SystemTime
    {
        public static Func<DateTime> Now = () => DateTime.Now;
    }
    

    你的代码变成:

    Entity.LastChange = SystemTime.Now();
    

    你的测试将变成:

    SystemTime.Now = () => new DateTime(2000,1,1);
    repository.ResetFailures(failedMsgs); 
    SystemTime.Now = () => new DateTime(2000,1,2);
    var msgs = repository.GetAllReadyMessages(); 
    Assert.AreEqual(2, msgs.Length);
    
        4
  •  9
  •   Michael Borgwardt    16 年前

    始终将核心日期处理逻辑(在我的经验中,这通常很容易为此而隔离)放在以日期为参数的单独方法中。

    然后,在您的生产代码中,您可以调用这些代码,并将当前日期作为参数提供给它们,并且仍然可以在单元测试中对边缘案例进行测试。

    顺便说一句,我发现非常关键的是要测试边缘案例的日期逻辑。它消除了(可能是灾难性的,参考文献Zune)你从来没有发现过的错误。除了你提到的那些,夏令时开关也可能是有问题的。

        5
  •  3
  •   Natrium    16 年前

    你可以制作一个模拟日期时间的mockobject。

    下面是一篇文章,其中有一个示例,其中datetime.now正在被模拟: http://geekswithblogs.net/AzamSharp/archive/2008/04/27/121695.aspx

        6
  •  3
  •   Mendelt    16 年前

    你已经给出了答案。可以围绕datetime函数编写一个小的包装类,然后将该类注入需要获取当前日期的类中。将datetime.now替换为对包装对象的调用。

    在测试中,您可以注入一个存根或模拟对象,为您提供所需的日期。

    另一种解决方案可能是,根据代码的工作方式,将日期作为参数传递,而不是隐藏对日期时间的调用。这使得您的代码更加可重用,因为它可以在超过当前日期的情况下工作。

        7
  •  2
  •   yfeldblum    16 年前
    public interface ITimeProvider {
        DateTime Now { get; }
    }
    
    public class SystemTimeProvider : ITimeProvider {
        public virtual DateTime Now { get { return DateTime.Now; } }
    }
    
    public class MockTimeProvider : ITimeProvider {
        public virtual DateTime Now { get; set; }
    }
    
    public class ServiceThatDependsOnTime {
        protected virtual ITimeProvider { get; set; }
        public ServiceThatDependsOnTime(ITimeProvider timeProvider) {
            TimeProvider = timeProvider;
        }
        public virtual void DoSomeTimeDependentOperation() {
            var now = TimeProvider.Now;
            //use 'now' in some complex processing.
        }
    }
    
    [Test]
    public virtual void TestTheService() {
        var time = new MockTimeProvider();
        time.Now = DateTime.Now.AddDays(-500);
        var service = new ServiceThatDependsOnTime(time);
        service.DoSomeTimeDependentOperation();
        //check that the service did it right.
    }
    
        8
  •  1
  •   Bruno Brant    11 年前

    为什么不使用假货?使用Visual Studio尽可能简单。只需在系统中添加一个假程序集,在测试中添加ShimsContext,

    using (ShimsContext.Create())
    {
    

    并设置日期时间。现在返回特定值:

    System.Fakes.ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);
    

    这种方法可以确保对datetime的每次调用。现在都被重定向到代码,因此您不必注入任何东西来获取当前时间(这应该是非常直接的),也不需要一个全局的、可写的变量。

        9
  •  0
  •   Gerrie Schenck    16 年前

    依赖注入可以解决这个问题,是的。

    您将要做的是创建一个IDateTimeProvider接口,该接口带有一个方法:getDate()。在生产代码类中,您将实现返回日期时间。

    当单元测试时,如Natrium建议的那样,您可以用一个模拟对象来替换它,该对象返回一个特定的测试日期。