基于.NETxUnit.net测试框架,开发自动猫门逻辑,允许白天开门,晚上锁门。在本系列的第一篇文章中,我演示了如何使用设计失败来确保代码中的预期结果。在第二篇文章中,我将继续研究示例项目:一个白天打开晚上锁上的自动猫门。提醒一下,您可以按照此处的说明使用.NET的xUnit.net测试框架。回想一下测试驱动开发(TDD)围绕大量单元测试进行的白天时间。在第一篇文章中实现了满足Given7pmReturnNighttime单元测试预期的逻辑。但这还不是全部,现在,您需要描述当当前时间大于7点时预期会发生什么。这是新的单元测试,称为Given7amReturnDaylight:[Fact]publicvoidGiven7amReturnDaylight(){varexpected="Daylight";varactual=dayOrNightUtility.GetDayOrNight();断言。等于(预期,实际);现在,新的单元测试失败了(越快越好!):开始测试执行,请稍候...[Xunit.net00:00:01.23]unittest.UnitTest1.Given7amReturnDaylight[FAIL]Failedunittest.UnitTest1.Given7amReturnDaylight[...]预期会收到Daylight的字符串值,但实际上会收到Nighttime的值。仔细分析失败的测试用例,似乎是代码本身出了问题。事实证明,GetDayOrNight方法的实现是不可测试的!看看我们的核心挑战:GetDayOrNight依赖于隐藏的输入。dayOrNight的值取决于隐藏的输入(它从内置系统时钟获取时间值)。GetDayOrNight包含非确定性行为。从系统时钟获取的时间值未定义。(因为)那个时间取决于您运行代码的时间,我们认为这是不可预测的。GetDayOrNightAPI质量差。API与具体数据源(系统DateTime)紧密耦合。GetDayOrNight违反了单一职责原则。这种方法可以同时使用和处理信息。一个方法应该负责执行一个功能是一种很好的做法。GetDayOrNight有多种更改原因。可以想象内部时间源可能发生变化的情况。同样,很容易想象处理逻辑也会发生变化。这些变化的不同原因必须相互隔离。当(我们)尝试了解GetDayOrNight的行为时,发现其API签名不足。理想情况下,只需查看API的签名即可让您了解API期望的行为类型。GetDayOrNight依赖于全局共享可变状态。不惜一切代价避免共享可变状态!即使阅读了源代码,也无法预测GetDayOrNight方法的行为。这是一个严重的问题。阅读源代码应该始终清楚,一旦系统运行,就可以预测系统的行为。失败背后的原则每当遇到工程问题时,建议使用久经考验的分而治之策略。在这种情况下,遵循关注点分离原则是一种可行的方法。关注点分离(SoC)是一种设计原则,用于将计算机程序划分为不同的模块,以便每个模块可以解决一个问题。关注点是影响计算机程序代码的一组信息。关注点可以像优化代码的硬件细节一样笼统,也可以像被实例化的类的名称一样具体。将SoC完美体现的程序称为模块化程序。(来源)GetDayOrNight方法应该只关心确定日期和时间值是代表白天还是夜晚。它不应该与寻找该价值的来源有任何关系。这个问题应该留给调用客户端。它必须留给调用客户端来获取当前时间。这种方法符合另一个有价值的工程原则——控制反转。MartinFowler在这里详细探讨了这个概念。框架的一个重要特征是用于自定义框架的用户定义方法通常来自框架本身,而不是从用户的应用程序代码中调用。该框架通常充当协调和排序应用程序活动的主程序。这种控制反转使框架能够充当可扩展框架。用户提供的方法为框架中的特定应用程序定制泛化算法。–RalphJohnson和BrianFoote重构测试用例因此,需要重构代码。摆脱对内部时钟的依赖(DateTime系统实用程序):DateTimetime=newDateTime();删除上面的代码(应该是文件中的第7行)。通过将输入参数DateTime时间添加到GetDayOrNight方法来进一步重构代码。这是重构的类DayOrNightUtility.cs:if(time.Hour>=7&&time.Hour<19){dayOrNight="日光";}返回dayOrNight;}}}重构代码需要更改单元测试。需要准备nightHour和dayHour的测试数据,并将这些值传递给GetDayOrNight方法。这是重构的单元测试:usingSystem;usingXunit;usingapp;namespaceunittest{publicclassUnitTest1{DateTimenightHour=newDateTime(2019,08,03,19,00,00);DateTimedayHour=newDateTime(2019,08,03,07,00,00);[事实]publicvoidGiven7pmReturnNighttime(){varexpected="Nighttime";varactual=dayOrNightUtility.GetDayOrNight(nightHour);断言。等于(预期,实际);}[事实]publicvoidGiven7amReturnDaylight(){varexpected="Daylight";varactual=dayOrNightUtility.GetDayOrNight(dayHour);断言。等于(预期,实际);}}}经验教训继续发展这个在开始一个简单的场景之前,请回顾一下你在这个练习中学到的东西。运行不可测试的代码很容易无意中制造陷阱。从表面上看,这样的代码似乎工作正常。然而,按照测试驱动开发(TDD)的做法,首先描述期望的结果,然后再描述实现,这暴露了代码中的严重问题。这表明TDD是确保代码不会太乱的理想方式。TDD指出了一些问题领域,例如缺乏单一职责和隐藏输入的存在。此外,TDD有助于删除非确定性代码,并将其替换为具有明确行为的完全可测试代码。最后,TDD有助于交付易于阅读且合乎逻辑的代码。
