BDD 入门
使用 SpecFlow 和 WatiN 进行行为驱动开发
下载示例代码
随着自动化单元测试在软件开发中变得越来越普遍,对各种“测试优先”方法的采用也呈现出相同的趋势。 这些实践为开发团队既带来了难得的机遇,也带来了独特的挑战,但所有这些机遇和挑战都是为了帮助从业人员建立“根据设计进行测试”的思路。
但是在“测试优先”时代的大多数时间,用于表达用户行为的方法一直贯穿于使用系统语言(一种与用户的语言不相关的语言)编写的单元测试。 随着行为驱动开发 (BDD) 技术的问世,这种情况也随之改变。 利用 BDD 技术,您可使用业务语言来编写自动化测试,同时还可保持与已实现系统的连接。
当然,现在我们创建了很多工具,可帮助您在开发过程中实现 BDD。 这些工具包括 Ruby 中的 Cucumber 以及适用于 Microsoft .NET Framework 的 SpecFlow 和 WatiN。 SpecFlow 可帮助您在 Visual Studio 中编写和执行规范,而 WatiN 可用于驱动浏览器进行自动化的端到端系统测试。
本文中,我将简要概述 BDD,然后解释 BDD 周期如何通过用于驱动单元级别实现的功能级别测试来包括传统的测试驱动开发 (TDD) 周期。 在介绍“测试优先”方法的基本内容后,我将介绍 SpecFlow 和 WatiN,并向您演示如何将这些工具与 MSTest 结合使用来为您的项目实现 BDD 的示例。
自动化测试简史
敏捷软件发展过程中产生的最有价值的实践之一就是测试优先的自动化开发模式,通常称为“测试驱动开发”或“TDD”。 TDD 的一条关键原则就是,测试创建不仅与设计和开发指南相关,同样还与验证和回归相关。 测试创建还涉及到使用测试来指定一组所需的功能,以及稍后通过测试来只编写实现该功能所需的代码。 因此,实现任何新功能的第一步就是通过一个失败测试来描述您的期望(参见图 1)。
图 1 测试驱动开发的周期
许多开发人员和团队已通过 TDD 取得了巨大的成功, 而其他人没有使用 TDD,结果发现自己长期以来疲于应付进程管理工作,尤其是,随着测试量开始增长,这些测试的灵活性却开始降低,情况会更糟糕。 尽管有些人觉得 TDD 很容易上手,而有些人却不清楚如何开始使用 TDD,结果只能将它放在一边,眼睁睁地看着最终期限的临近和工作的大量积压而束手无策。 最后,许多对此感兴趣的开发人员遇到了其组织内部对这项工作的重重阻力,要么是因为“测试”这个词暗示这项职能属于另一个团队,或是因为“TDD 产生了太多额外的代码并减缓了项目进度”这个错误的观念。
Steve Freeman 和 Nat Pryce 在他们的著作“Growing Object-Oriented Software, Guided by Tests”(Addison-Wesley Professional, 2009) 中指出,“传统的”TDD 缺少真正的“测试优先”开发的某些优点:
“通过为应用程序中的类编写单元测试来开始 TDD 过程是有风险的。 这不仅比不进行任何测试要好得多,还可以发现那些我们所熟知但又无法避免的常见编程错误…但是项目仅进行单元测试却会让 TDD 过程的重要好处大打折扣。 我们也看到了,有些具有高品质和经过严格单元测试的代码的项目并非从任何位置都可以调用,或者这些项目无法与系统的其余部分集成,因而必须重写。”
2006 年,Dan North 在 Better Software 杂志 (blog.dannorth.net/introducing-bdd) 中的一篇文章中提到了许多这类难题。 在他的文章中,North 介绍了三年来在测试实践方面所采用的一系列做法。 尽管这些实践就本身而言仍属于 TDD 的范畴,但却促使 North 采用一种更加侧重分析的观点来看待测试,并创造了术语“行为驱动开发”以概括这种转换。
BDD 最常用的一个应用尝试通过验收测试或可执行规范来强化创建测试的重点和过程,从而扩展 TDD。 每个规范将作为进入开发周期的一个入口点,它从用户角度以分步骤的形式介绍系统的行为方式。 完成编写后,开发人员将使用规范及其现有的 TDD 过程来实现足量的生产代码,从而得到一个通过测试的方案(参见图 2)。
图 2 行为驱动开发的周期
从何处开始设计
大多数人认为 BDD 是 TDD 的超集,而不是它的替代品。 两者的重要区别是对初始设计和测试创建的侧重点不同。 与 TDD 侧重于针对单元或对象的测试不同,我将以用户的目标以及他们为了实现这些目标而采取的步骤为侧重点。 因为我不再从小型单元的测试着手,所以我也不太愿意考虑具体用法或设计细节。 我更多的是记录能够证明系统合适的可执行规范。 我仍然编写单元测试,但是 BDD 鼓励采用由外而内的方法,该方法首先要提供所要实现的功能的完整说明。
让我们看看此差异的示例。 在传统的 TDD 实践中,您可以在图 3 中编写测试,以便演练 CustomersController 的 Create 方法。
图 3 针对创建客户的单元测试
[TestMethod]
public void PostCreateShouldSaveCustomerAndReturnDetailsView() {
var customersController = new CustomersController();
var customer = new Customer {
Name = "Hugo Reyes",
Email = "hreyes@dharmainitiative.com",
Phone = "720-123-5477"
};
var result = customersController.Create(customer) as ViewResult;
Assert.IsNotNull(result);
Assert.AreEqual("Details", result.ViewName);
Assert.IsInstanceOfType(result.ViewData.Model, typeof(Customer));
customer = result.ViewData.Model as Customer;
Assert.IsNotNull(customer);
Assert.IsTrue(customer.Id > 0);
}