框架重构:测试中的DateTime.Now

存在的问题

  • DateTime.Now是C#语言中获取计算机的当前时间的代码;
  • 但是,在对使用了DateTime.Now的方法进行测试时,由于计算机时间的实时性,期望值一直在变化。如:计算年龄。
public static class DateTimeExtensionMethods
{
    public static int Age(this DateTime date)
    {
        DateTime now = DateTime.Now;
        ......
    }
}
new DateTime(2008,8,12).Age().ShouldEqual(???);

解决方案一:给Age方法增加一个参数,将当前时间传进去

// method
public static int Age(this DateTime date, DateTime now){...}
// test
new DateTime(2008,8,12).Age(new DateTime(2017,8,13)).ShouldEqual(9);

缺点:

  • 显示传入当前时间麻烦
  • 多一个参数,维护成本也会增加
  • 感觉怪怪的,不符合习惯

解决方案二:使用IoC框架实现

public interface ISystemClock
{
    DateTime Now { get; }
}
public class SystemClock : ISystemClock
{
    public DateTime Now 
    { 
        get { return DateTime.Now; } 
    }
}

public static class DateTimeExtensionMethods
{
    public static int Age(this DateTime date)
    {
        DateTime now = IoC.Get<ISystemClock>().Now;
        ......
    }
}
// 真实系统
IoC.Register<ISystemClock, SystemClock>();
// test
var mock = MockRepository.GenerateMock<ISystemClock>();
mock.Stub(x=>x.Now).Return(new DateTime(2017,8,13);
IoC.Register<ISystemClock>(mock);

缺点

  • 需要使用IoC框架
  • 操作繁琐,代码量有点多

解决方案三:使用委托(当前最佳方案)

public static class SystemClock
{
    public static Func<DateTime> Now = () => DateTime.Now;
}
public static class DateTimeExtensionMethods
{
    public static int Age(this DateTime date)
    {
        DateTime now = SystemClock.Now();
        int age = now.Year - date.Year;
        if (now.Month == date.Month)
            age = (now.Day < date.Day) ? age - 1 : age;
        else if (now.Month < date.Month)
            age = age - 1;
        return age;
    }
}
[Subject(typeof(DateTime), "Age")]
public class when_convert_birthday_to_age
{
    Establish context = () => SystemClock.Now = () => new DateTime(2013, 8, 25);

    public class with_yesterday_is_birthday
    {
        Because of = () => result = new DateTime(1980, 8, 24).Age();
        It 应该计算出正确的年龄 = () => result.ShouldEqual(33);
    }
        
    public class with_today_is_birthday
    {
        Because of = () => result = new DateTime(1980, 8, 24).Age();
        It 应该计算出正确的年龄 = () => result.ShouldEqual(33);
    }

    public class with_tomorrow_is_birthday
    {
        Because of = () => result = new DateTime(1980, 8, 26).Age();
        It 应该计算出正确的年龄 = () => result.ShouldEqual(32);
    }

    private static int result;
}

You can implement ICleanupAfterEveryContextInAssembly to perform cleanup after every context.
machine.specifications官网

// 每个测试执行完后,需把SystemClock.Now还原
public class ResetTheClock : ICleanupAfterEveryContextInAssembly
{
    public void AfterContextCleanup()
    {
        SystemClock.Now = () => DateTime.Now;
    }
}
posted @ 2017-04-23 09:06  屠一刀  阅读(494)  评论(0编辑  收藏  举报