今天文章的内容是参考於 C# - how to inject, mock or stub DateTime for unit tests,在 C# 中 DateTime 是专门帮我们处理时间的资料结构,其好处是可以帮我们纪录年月日时分秒,也可以透过程序呼叫现在的时间点,但呼叫现在的时间这点会成为单元测试中不稳定的因素;因此,今天的议题就是来探讨如何在撰写 DateTime.Now 的情境中抽离并验证其商业逻辑是否正确,以下为使用 DateTime.Now 的范例:
public class Decision
{
public string WhatToDo()
{
var currentDateTime = DateTime.Now;
if (currentDateTime.Hour > 8 && currentDateTime.Hour < 18)
{
return Work();
}
else if (currentDateTime.Hour > 18 && currentDateTime.Hour < 22)
{
return Exercise();
}
else
{
return Sleep();
}
}
private string Work()
{
return "Work!";
}
private string Exercise()
{
return "Exercise!";
}
private string Sleep()
{
return "Sleep!";
}
}
而截选本文主要原因是里面运用了写实务上系统很常用的 DateTime.Now 外,另一方面也提出了许多接缝的手法。所以接下来来探讨如何抽离 DateTime.Now 的手法吧!而本文提出了五种方向,如下:
那我们逐一来看吧~
第一个为新增时间包覆器的类别 —— DateTimeWrapper,里面包含 thisDateTime 属性、两种建构函式与 Now 方法,如下:
public class DateTimeWrapper
{
private DateTime? thisDateTime;
public DateTimeWrapper()
{
thisDateTime = null;
}
public DateTimeWrapper(DateTime fixedDateTime)
{
thisDateTime = fixedDateTime;
}
public DateTime Now { get { return thisDateTime ?? DateTime.Now; } }
}
因此,就可以开始撰写商业逻辑,如下:
public class Decision
{
private readonly DateTimeWrapper thisDateTimeWrapper;
public Decision(DateTimeWrapper inThisDateTimeWrapper)
{
thisDateTimeWrapper = inThisDateTimeWrapper;
}
public string WhatToDo()
{
var currentDateTime = thisDateTimeWrapper.Now;
if (currentDateTime.Hour > 8 && currentDateTime.Hour < 18)
{
return Work();
}
else if (currentDateTime.Hour > 18 && currentDateTime.Hour < 22)
{
return Exercise();
}
else
{
return Sleep();
}
}
private string Work()
{
return "Work!";
}
private string Exercise()
{
return "Exercise!";
}
private string Sleep()
{
return "Sleep!";
}
}
[Test]
public void WorkTest()
{
// Arrange
var dateTimeWrapper = new DateTimeWrapper(new DateTime(2020, 01, 01, 08, 00, 00));
var decision = new Decision(dateTimeWrapper);
// Act
var whatToDo = decision.WhatToDo();
// Assert
Assert.Equal("Work!", whatToDo);
}
[Test]
public void ExerciseTest()
{
// Arrange
var dateTimeWrapper = new DateTimeWrapper(new DateTime(2020, 01, 01, 18, 00, 00));
var decision = new Decision(dateTimeWrapper);
// Act + Assert
// ... 结构与 WorkTest 都一样
}
[Test]
public void SleepTest()
{
// Arrange
var dateTimeWrapper = new DateTimeWrapper(new DateTime(2020, 01, 01, 23, 00, 00));
var decision = new Decision(dateTimeWrapper);
// Act + Assert
// ... 结构与 WorkTest 都一样
}
这样写好处在於,若处理既有程序码,可更动少量的程序码即达到做为测试的接缝。
第二个为假物件框架 (NSubstitute),就要提到我们最爱提的把商业逻辑介面化,从前言可看出 DateTime.Now 已经和 WhatToDo 耦合再一起,若要使用虚设常式抽离并实作,则先需新增一个时间的介面,如下:
public interface IDateTimeWrapper
{
public DateTime Now { get { return DateTime.Now; } }
}
public class DateTimeWrapper : IDateTimeWrapper {}
因此,商业逻辑就改写如下:
public class Decision
{
private readonly IDateTimeWrapper DateTimeWrapper;
public Decision(IDateTimeWrapper inDateTimeWrapper)
{
DateTimeWrapper = inDateTimeWrapper;
}
public string WhatToDo()
{
var currentDateTime = DateTimeWrapper.Now;
// ... 後面都一样
}
// ... 後面都一样
}
因此,就可以撰写测试码,而撰写测试码时,IDateTimeWrapper 的 Now 就可利用 NSubstitute 中的 Returns 注入相对应的属性,如下:
[Test]
public void WorkTest()
{
// Arrange
var dateTimeWrapper = Substitute.For<IDateTimeWrapper>();
dateTimeWrapper.Now.Returns(new DateTime(2020, 01, 01, 08, 00, 00));
var decision = new Decision(dateTimeWrapper);
// Act
var whatToDo = decision.WhatToDo();
// Assert
Assert.Equal("Work!", whatToDo);
}
[Test]
public void ExerciseTest()
{
// Arrange
var dateTimeWrapper = Substitute.For<IDateTimeWrapper>();
dateTimeWrapper.Now.Returns(new DateTime(2020, 01, 01, 18, 00, 00));
var decision = new Decision(dateTimeWrapper);
// Act + Assert
// ... 结构与 WorkTest 都一样
}
[Test]
public void SleepTest()
{
// Arrange
var dateTimeWrapper = Substitute.For<IDateTimeWrapper>();
dateTimeWrapper.Now.Returns(new DateTime(2020, 01, 01, 23, 00, 00));
var decision = new Decision(dateTimeWrapper);
// Act + Assert
// ... 结构与 WorkTest 都一样
}
这种撰写方式与我们先前所写的 Code Style 比较一致,且不需要手刻虚设常式,程序码较简洁。
那因文章篇幅,其他的方式会在明天一一说明,并统整比较五个方法之前的效益。
>>: 22.unity读取文字文件并分行(TextAsset、Split)
路由架构 Breeze 已经架构好利用 inertia.js 取得 Login 等画面的路由,不过为...
Components基本定义 Components是react组成的最基本元素,每一个Compont...
Search View在Odoo内非常常见,可以帮助使用者快速搜寻、过滤、分类需要的资料,因此透过设...
1. 语法糖 、...展开 https://codepen.io/Rouoxo/pen/ZEXXda...
终於要开始了:「说到底,单元测试怎麽做?」 单元测试 单元测试要测的是一个逻辑单元功能是否正确。这短...