在我的文章《突变测试是 TDD 的演进》中,我阐述了迭代在保证可度量测试可用时的解决方案方面的强大功能。在那篇文章中,迭代方法帮助确定了如何实现计算给定数字平方根的代码。
我还证明了最有效的方法是找到一个可度量的目标或测试,然后开始用最佳猜测进行迭代。对正确答案的第一个猜测很可能会失败,正如预期的那样,因此需要改进失败的猜测。必须根据可度量的目标或测试来验证改进后的猜测。根据结果,猜测要么被验证,要么必须进一步改进。
在这个模型中,学习如何达到解决方案的唯一方法是反复失败。这听起来违反直觉,但令人惊讶的是,它奏效了。
继该分析之后,本文探讨了在构建包含某些依赖项的解决方案时,使用 DevOps 方法的最佳方式。第一步是编写一个预期会失败的测试。
依赖项的问题在于您不能依赖它们
正如 Michael Nygard 在《没有终点的架构》中诙谐地表达的那样,依赖项的问题是一个巨大的话题,最好留到另一篇文章中讨论。在这里,您将研究依赖项可能给项目带来的潜在陷阱,以及如何利用测试驱动开发 (TDD) 来避免这些陷阱。
首先,提出一个现实生活中的挑战,然后看看如何使用 TDD 解决它。
谁放出了猫?

在敏捷开发环境中,通过定义期望的结果来开始构建解决方案是很有帮助的。通常,期望的结果在 用户故事 中描述
使用我的家庭自动化系统 (HAS),
我想控制猫何时可以外出,
因为我想确保猫在夜间的安全。
现在您有了一个用户故事,您需要通过提供一些功能需求(即,通过指定验收标准)来详细说明它。从伪代码中描述的最简单的场景开始
场景 #1:夜间禁用猫门
- 假设时钟检测到是夜间
- 当时钟通知 HAS 时
- 那么 HAS 禁用物联网 (IoT) 猫门
分解系统
您正在构建的系统(HAS)需要分解——分解为它的依赖项——然后您才能开始工作。您必须做的第一件事是识别任何依赖项(如果您幸运的话,您的系统没有依赖项,这将使其易于构建,但那么它可能不是一个非常有用的系统)。
从上面的简单场景中,您可以看到期望的业务结果(自动控制猫门)取决于检测夜间。这种依赖关系取决于时钟。但是时钟无法确定是白天还是晚上。这取决于您来提供该逻辑。
您正在构建的系统中的另一个依赖项是自动访问猫门并启用或禁用的能力。这种依赖关系很可能取决于物联网猫门提供的 API。
快速失败以进行依赖项管理
为了满足一个依赖项,我们将构建确定当前时间是白天还是夜晚的逻辑。本着 TDD 的精神,我们将从一个小小的失败开始。
有关如何设置开发环境和此练习所需支架的详细说明,请参阅我的上一篇文章。我们将重用相同的 .NET 环境并依赖于 xUnit.net 框架。
接下来,创建一个名为 HAS(代表“家庭自动化系统”)的新项目,并创建一个名为 UnitTest1.cs 的文件。在此文件中,编写第一个失败的单元测试。在此单元测试中,描述您的期望。例如,当系统运行时,如果时间是晚上 7 点,那么负责决定是白天还是夜晚的组件返回“Nighttime”值。
这是描述该期望的单元测试
using System;
using Xunit;
namespace unittest
{
public class UnitTest1
{
DayOrNightUtility dayOrNightUtility = new DayOrNightUtility();
[Fact]
public void Given7pmReturnNighttime()
{
var expected = "Nighttime";
var actual = dayOrNightUtility.GetDayOrNight();
Assert.Equal(expected, actual);
}
}
}
到目前为止,您可能已经熟悉单元测试的形状和形式。快速回顾:通过给单元测试一个描述性名称来描述期望,在本例中为 Given7pmReturnNighttime。然后在单元测试的主体中,创建一个名为 expected 的变量,并为其分配期望的值(在本例中为值“Nighttime”)。在此之后,为名为 actual 的变量分配实际值(在组件或服务处理一天中的时间后可用)。
最后,它检查期望是否已满足,方法是断言期望值和实际值相等:Assert.Equal(expected, actual)。
您还可以在上面的列表中看到一个名为 dayOrNightUtility 的组件或服务。此模块能够接收消息 GetDayOrNight,并应返回 string 类型的值。
再次强调,本着 TDD 的精神,正在描述的组件或服务尚未构建(它仅仅是被描述,目的是稍后规定它)。构建它是由描述的期望驱动的。
在 app 文件夹中创建一个新文件,并将其命名为 DayOrNightUtility.cs。将以下 C# 代码添加到该文件并保存
using System;
namespace app {
public class DayOrNightUtility {
public string GetDayOrNight() {
string dayOrNight = "Undetermined";
return dayOrNight;
}
}
}
现在转到命令行,将目录更改为 unittests 文件夹,并运行测试
[Xunit.net 00:00:02.33] unittest.UnitTest1.Given7pmReturnNighttime [FAIL]
Failed unittest.UnitTest1.Given7pmReturnNighttime
[...]
恭喜,您已经编写了第一个失败的单元测试。单元测试期望 DayOrNightUtility 返回字符串值“Nighttime”,但实际上,它收到了字符串值“Undetermined”。
修复失败的单元测试
修复失败测试的一种快速而简陋的方法是将值“Undetermined”替换为值“Nighttime”并保存更改
using System;
namespace app {
public class DayOrNightUtility {
public string GetDayOrNight() {
string dayOrNight = "Nighttime";
return dayOrNight;
}
}
}
现在当我们运行测试时,它通过了
Starting test execution, please wait...
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 2.6470 Seconds
但是,硬编码值基本上是作弊,因此最好赋予 DayOrNightUtility 一些智能。修改 GetDayOrNight 方法以包含一些时间计算逻辑
public string GetDayOrNight() {
string dayOrNight = "Daylight";
DateTime time = new DateTime();
if(time.Hour < 7) {
dayOrNight = "Nighttime";
}
return dayOrNight;
}
该方法现在从系统获取当前时间,并比较 Hour 值以查看它是否小于早上 7 点。如果是,则逻辑将 dayOrNight 字符串值从“Daylight”转换为“Nighttime”。单元测试现在通过了。
测试驱动解决方案的开始
我们现在有了基本用例单元测试的开始,以及针对我们时间依赖性的可行解决方案。还有很多用例需要处理。
在下一篇文章中,我将演示如何测试白天时间以及如何在此过程中利用失败。
评论已关闭。