测试脚本示例
在本系列的第一篇文章中,我演示了如何使用计划的故障来确保代码中的预期结果。 在第二篇文章中,我将继续开发示例项目-一个自动猫门,该门在白天开放,并在夜间锁定。
提醒一下,您可以按照此处的说明使用.NET xUnit.net测试框架。
白天的时间呢?
回想一下,测试驱动开发(TDD)围绕着大量的单元测试。
第一篇文章实现了满足Given7pmReturnNighttime单元测试期望的逻辑。 但是您还没有完成。 现在,您需要描述当前时间大于上午7点时发生的期望。 这是新的单元测试,称为Given7amReturnDaylight :
[ Fact
]
public
void Given7amReturnDaylight
(
)
{
var expected
=
"Daylight"
;
var actual
= dayOrNightUtility
.
GetDayOrNight
(
)
;
Assert
.
Equal
( expected, actual
)
;
}
现在,新的单元测试失败(非常希望尽早失败!):
Starting test execution, please wait...
[Xunit.net 00:00:01.23] unittest.UnitTest1.Given7amReturnDaylight [FAIL]
Failed unittest.UnitTest1.Given7amReturnDaylight
[...]
它期望接收字符串值“ Daylight”,但接收字符串值“ Nighttime”。
分析失败的测试用例
经过仔细检查,似乎代码已陷入陷阱。 事实证明, GetDayOrNight方法的实现是不可测试的!
看看我们面临的核心挑战:
- GetDayOrNight依赖隐藏的输入。
dayOrNight的值取决于隐藏的输入(它从内置系统时钟获取一天中时间的值)。 - GetDayOrNight包含不确定行为。
从系统时钟获得的一天中时间的值是不确定的。 这取决于运行代码的时间点,我们必须认为这是不可预测的。 - GetDayOrNight API的质量低。
该API与具体的数据源(系统DateTime )紧密耦合。 - GetDayOrNight违反了单一责任原则。
您已经实现了一种同时使用和处理信息的方法。 优良作法是一种方法应负责执行一项职责。 - GetDayOrNight有多个更改原因。
可以想象内部时间源可能会发生变化的情况。 同样,很容易想象处理逻辑会改变。 这些变化的不同原因必须相互隔离。 - 尝试了解其行为时,GetDayOrNight的API签名不足。
只需查看API的签名,就能了解API预期的行为类型。 - GetDayOrNight取决于全局共享可变状态。
不惜一切代价避免共享的可变状态! - 即使在阅读源代码之后,也无法预测GetDayOrNight方法的行为。
这是一个可怕的主张。 通过阅读源代码,应该始终非常清楚,一旦系统开始运行,便可以预测出哪种行为。
失败背后的原理
每当您遇到工程问题时,建议使用久经考验的分而治之策略。 在这种情况下,遵循关注点分离的原则是可行的方法。
关注点分离 ( SoC )是一种将计算机程序分为不同部分的设计原理,以便每个部分都可以解决一个单独的关注点。 关注点是影响计算机程序代码的一组信息。 问题可以像针对代码进行优化的硬件的细节一般,也可以像要实例化的类的名称一样具体。 完美体现SoC的程序称为模块化程序。
( 来源 )
GetDayOrNight方法仅应与确定日期和时间值表示白天还是夜晚有关。 它不应该与寻找该价值的来源有关。 该问题应留给主叫客户。
您必须将其留给主叫客户,以获取当前时间。 这种方法与另一个有价值的工程原理相一致-控制反转 。 马丁·福勒(Martin Fowler)在这里详细探讨了这个概念。
框架的一个重要特征是,用户定义的用于定制框架的方法通常是从框架本身而不是从用户的应用程序代码调用的。 该框架通常在协调和排序应用程序活动中扮演主程序的角色。 控制权的这种反转使框架有能力充当可扩展的框架。 用户提供的方法可针对特定应用量身定制框架中定义的通用算法。
重构测试用例
因此,代码需要重构。 摆脱对内部时钟的依赖( DateTime系统实用程序):
DateTime time = new DateTime ( ) ;
删除以上行(文件中应为第7行)。 通过将输入参数DateTime时间添加到GetDayOrNight方法,进一步重构代码。
这是重构的类DayOrNightUtility.cs :
using
System
;
namespace app
{
public
class DayOrNightUtility
{
public
string GetDayOrNight
( DateTime time
)
{
string dayOrNight
=
"Nighttime"
;
if
( time
.
Hour
>=
7
&& time
.
Hour
<
19
)
{
dayOrNight
=
"Daylight"
;
}
return dayOrNight
;
}
}
}
重构代码需要更改单元测试。 您需要为nightHour和dayHour准备值,并将这些值传递到GetDayOrNight方法中。 这是重构的单元测试:
using
System
;
using
Xunit
;
using
app
;
namespace unittest
{
public
class UnitTest1
{
DayOrNightUtility dayOrNightUtility
=
new DayOrNightUtility
(
)
;
DateTime nightHour
=
new DateTime
(
2019 , 08, 03,
19 , 00, 00
)
;
DateTime dayHour
=
new DateTime
(
2019 , 08, 03, 07, 00, 00
)
;
[ Fact
]
public
void Given7pmReturnNighttime
(
)
{
var expected
=
"Nighttime"
;
var actual
= dayOrNightUtility
.
GetDayOrNight
( nightHour
)
;
Assert
.
Equal
( expected, actual
)
;
}
[ Fact
]
public
void Given7amReturnDaylight
(
)
{
var expected
=
"Daylight"
;
var actual
= dayOrNightUtility
.
GetDayOrNight
( dayHour
)
;
Assert
.
Equal
( expected, actual
)
;
}
}
}
得到教训
在继续进行此简单方案之前,请回顾一下并回顾本练习中的课程。
通过实施无法测试的代码,很容易在不经意间创建陷阱。 从表面上看,这样的代码似乎正常工作。 但是,遵循测试驱动开发(TDD)的实践(首先描述期望,然后才描述实现),暴露了代码中的严重问题。
最后,TDD帮助交付了易于阅读的代码和易于遵循的逻辑。
在本系列的下一篇文章中,我将演示如何使用在本练习中创建的逻辑来实现功能代码,以及如何进行进一步的测试使其变得更好。
翻译自: https://opensource.com/article/19/9/mutation-testing-example-failure-experimentation
测试脚本示例