锻造坊 - Cucumber

作者:Reuven M. Lerner

人们过去常说,开源技术擅长模仿和复制专有项目,但他们几乎没有自己的原创想法。这从来都不是完全正确的,但我相信今天这显然是错误的,而且随着时间的推移,它变得越来越错误。在许多方面,计算机行业的创新前沿正在开源社区内发生。如果您正在寻找操作系统、网络协议、Web 开发或编程技术的未来,那么您很可能会在开源中找到它。

Ruby on Rails,这个著名的 Web 开发框架,就是这样一种技术;现在几乎每种其他语言都有了 Rails 的等价物。Rails 肯定在许多方面影响了其他框架,从它对 MVC 的使用到其普遍的“约定优于配置”的立场。但是,Rails 也通过其对自动化测试的使用和鼓励而处于领先地位。测试在计算机世界中已经存在一段时间了,但 Ruby 社区,尤其是 Rails 社区,几乎狂热地希望在多个层面上广泛测试程序。正如多年来许多其他人所写的那样,这种测试为您在更改、改进和仅仅重构现有代码时提供了所需的信心。

在过去的几个月中,我一直在研究许多来自 Ruby 和/或 Rails 世界的测试技术,我相信这些技术很可能会对其他行动较慢的语言和环境产生影响。所有这些测试的共同点是,它们主要引起程序员的兴趣。也就是说,从事项目的程序员都可能同意测试的必要性,甚至同意进行测试的框架。项目的非技术经理虽然可能会从这种测试中受益,但他们并没有非常密切地参与此过程,因为他们通常无法这样做。毕竟,即使 Shoulda 的语法,就其相对于标准 test/unit 构造的简单性而言,仍然在代码中,因此非程序员很难阅读和理解。

Cucumber 出现了,这是一个名字古怪但功能极其强大的集成测试框架,由 Aslak Hellesoy 开始开发。Cucumber 是 BDD(行为驱动设计)思想流派的一部分,该思想认为开发应该从规范开始,然后编写代码以匹配该规范。

通常,在 RSpec 和 Shoulda 等框架中,规范是用代码编写的。Cucumber 采用了不同的方法,使以英语或其他自然语言编写规范成为可能,并让计算机负责将这些规范翻译成可执行格式。这样,规范仍然是可执行的,但非程序员也可以阅读它们,从而更容易在会议或文档中讨论。我没有亲身体验过这一点,但非程序员应该可以阅读 Cucumber 故事并开始执行它们。

Cucumber 可以用于各种测试环境中,但 Rails 程序员最常将其用于集成测试,并且许多使用它的人都在对其开发过程的影响赞不绝口。本月,我将介绍 Cucumber,我相信它有潜力彻底改变集成测试的编写和执行方式,并且是开源社区成员的另一项巧妙发明。我将逐步介绍一些简单的 Cucumber 测试的创建,并指出它在哪里需要更多成熟。(蔬菜笑话是 Cucumber 社区的主要内容,无论好坏。因此,如果您计划使用 Cucumber,如果您觉得这种幽默很有趣,那将很有用。)

安装和使用 Cucumber

仅在过去一年中,Cucumber 就经历了许多快速转变,这在很大程度上归功于越来越多的贡献者,以及 Ruby 社区内的巨大兴奋和曝光。因此,有时很难跟上版本号和文档。

幸运的是,Cucumber 的安装过程仍然相当简单;它被打包为 Ruby gem,这意味着您可以使用以下命令安装它

gem install cucumber

在撰写本文时,Cucumber 的版本为 0.4。此外,虽然 Cucumber(和许多其他 Ruby gem)一直由 GitHub(一个著名的 Git 版本控制系统的商业存储库)托管,但最近宣布 GitHub 将不再支持 Ruby gem 的创建。因此,当您阅读本文时,您可能需要四处寻找 Cucumber 的官方存储库。

安装 Cucumber 后,您需要将其功能添加到您的 Rails 应用程序中。您可以使用以下命令执行此操作

script/generate cucumber

这会将适当的 Rake 任务放入适当的位置(在 lib/tasks/cucumber.rake 中),添加初始默认步骤定义(即,低级测试实现)和 Cucumber 运行所需的整体系统支持。与 Cucumber 关联的所有文件都放在 features 目录中,这对于新手来说可能有些令人困惑。

一旦这些新文件就位,您就可以按如下方式运行 Cucumber

rake cucumber

Cucumber 将运行 features 目录中任何带有 .feature 后缀的文件。如果您刚刚安装了 Cucumber,则尚不存在此类文件,您将看到如下输出

0 scenarios
0 steps
0m0.000s
Loaded suite /usr/bin/rake
Started

Finished in 0.000232 seconds.

0 tests, 0 assertions, 0 failures, 0 errors

这类似于您运行以下命令得到的输出rake test没有安装任何测试。所以让我们开始编写一些东西。

Cucumber 使用与您可能习惯的不同的词汇表来创建测试和规范。每个 Cucumber 文件描述应用程序的单个“功能”,并具有 .feature 后缀。功能通常是应用程序的一小部分——从身份验证到发送或接收消息,再到生成报告。传统上,功能是用如下行描述的

Feature: Home page

如您所见,此文件以单词“Feature:”开头,然后包含描述。此描述与 Cucumber 中的许多其他描述一样,稍后会出现在输出中,作为有用的描述。

在 Feature 声明之后,您描述功能,通常以“故事”的形式,正如在许多敏捷团队和 BDD 世界中普遍使用的那样。(如果您不熟悉故事,我建议阅读 Dan North 关于此主题的博客文章;请参阅资源。)这是一个典型的故事

As a user,
I want to be able to log in
So that I can use the system

然后以多种方式测试该功能,每种方式都称为场景。这个想法是,每个场景描述了一个用户执行某些操作 (When) 的情况 (Given),然后看到一些结果 (Then)。场景应尽可能具体,测试通过应用程序界面的特定路径。

请注意,场景并非旨在测试代码的某个特定控制器、模型、库或其他元素。相反,场景应代表用户角度的特定操作,该操作可能涵盖一个控制器和一个模型,或每个控制器和模型十几个。每个功能有多个场景是正常且合理的。同样可以合理地假设,随着您(和您的用户)以新的和不同的方式强调应用程序,并且您发现需要新场景覆盖的错误,场景的数量会随着时间的推移而增长。一个场景由一个或多个步骤组成,这些步骤被翻译成可工作的 Ruby 代码。这是一个示例场景

Scenario: Get to the login screen

      Given a user named "Reuven" "Lerner" with an e-mail 
      address "reuven@lerner.co.il"
      When I go to the home page
      Then I should see "Web site"
      Then I should see "Login"

我将此场景(及其故事和一个功能)放入 features/login.feature 中,然后运行rank cucumber。Cucumber 通过浏览文件,执行我定义的场景来响应。好吧,它试图执行该场景;这是我实际在屏幕上看到的

Feature: Home page

As a user,
I want to be able to log in
So that I can use the system

  Scenario: Get to the login screen      
  # features/login.feature:7
    Given a user named "Reuven" "Lerner" with an e-mail
     ↪address "reuven@lerner.co.il" # features/login.feature:9
      Undefined step: "a user named "Reuven"" (Cucumber::Undefined)
      features/login.feature:9:in `Given a user named "Reuven" "Lerner" 
      with an e-mail address "reuven@lerner.co.il"'
    When I go to the home page     
    # features/step_definitions/webrat_steps.rb:15
    Then I should see "Web site"   
    # features/step_definitions/webrat_steps.rb:123
   Then I should see "Login"       
   # features/step_definitions/webrat_steps.rb:123

1 scenario (1 undefined)
4 steps (3 skipped, 1 undefined)
0m0.012s
You can implement step definitions for undefined steps 
 with these snippets:

Given /^a user named "([^\"]*)" "([^\"]*)" with an 
 ↪e-mail address "([^\"]*)"$/ do
|arg1, arg2, arg3|
  pending
end

rake aborted!
Command failed with status (1): 
 [/System/Library/Frameworks/Ruby.framework/...]

换句话说,Cucumber 寻找可以处理我的步骤“Given a user”的定义,但没有找到。它停止解释我的场景并抛出了一个错误。然后 Cucumber 更进一步,提醒我需要定义此步骤,并为我提供了它的轮廓。

从 Cucumber 的建议中可以看出,步骤定义是一个附加到 Ruby 代码块的正则表达式。正则表达式与 Given(或 When 或 Then)语句匹配,使用括号(正则表达式中匹配的标准方式)匹配一项,然后将其作为参数传递给代码块。

现在,让我们采用简单的步骤定义并将其粘贴到 features/step_definitions/authentication.rb 中。当重新运行时rake cucumber,Cucumber 不再抱怨未定义此步骤定义。相反,它表示由于步骤定义处于挂起状态,因此它无法继续执行场景的其余部分。让我们定义此 Given 步骤

Given /^a user named "([^\"]*)" "([^\"]*)" with an 
 ↪e-mail address "([^\"]*)"$/ do
|first_name, last_name, email|
    @person = Person.create(:first_name => first_name,
                            :last_name => last_name,
                            :password => 'password',
                            :email_address => email)
end

您可能已经注意到,此步骤定义与原始定义不同,原始定义期望两个带引号的单词而不是一个,并且代码块采用两个参数而不是一个参数。让我们更改场景定义,使其包含步骤

Given a user named "Reuven" "Lerner" with an e-mail
 address "reuven@lerner.co.il"

在 Cucumber 中运行此命令会得到以下结果

Scenario: Users who go to the home page are asked to log in
# features/login.feature:7
     
Given a user named "Reuven" "Lerner" with an e-mail 
 ↪address "reuven@lerner.co.il" 
 # features/step_definitions/authentication.rb:1
  When I go to the home page   
  # features/step_definitions/webrat_steps.rb:15
  Then I should see "Web site" 
  # features/step_definitions/webrat_steps.rb:123
  And I should see "You must first log in" 
  # features/step_definitions/webrat_steps.rb:123

1 scenario (1 passed)
4 steps (4 passed)
0m0.473s
Loaded suite /usr/bin/rake
Started

Finished in 0.000167 seconds.

0 tests, 0 assertions, 0 failures, 0 errors

如果您想知道是谁定义了最后三个步骤,请查看输出的右侧:Webrat,一个用 Ruby 编写的浏览器模拟器,理解大量浏览器风格的步骤定义,包括“I go to”和“I should see”,允许您测试每种情况下文本的存在或不存在。Cucumber 提供了各种各样的 Webrat 步骤定义,以便您可以告诉 Cucumber 转到页面、填写表单或使用选择列表、复选框和单选按钮。

这基本上就是使用 Cucumber 的含义。您在 .feature 文件中创建一个功能,并在该 .feature 文件中编写一个或多个场景,这些场景的行与 step_definitions 目录中定义的正则表达式匹配。 .feature 文件以英文从用户的角度编写的事实意味着您可以将其展示给非技术经理或客户。他们甚至可以帮助编写场景,并且如果场景的编写对于 Cucumber 的目的而言不是完美的,他们可以理解您正在尝试从各种角度测试应用程序。

用 Cucumber 编写场景感觉有点奇怪(至少在我的经验中是这样),因为您基本上是在编写代码,但却是用完整的英语句子。我还花了一些时间来理解每个英语句子都类似于子例程调用,在 step_definitions 目录中调用特定的代码片段。随着时间的推移,您大概会创建一个庞大的此类步骤定义库,然后您可以在 Cucumber 场景中混合和匹配这些定义来测试您的系统。

这是我编写的第二个场景,用于测试登录

Scenario: Users can log in by entering their 
 name and e-mail address
Given a user named "Reuven" "Lerner" with an e-mail
 ↪address "reuven@lerner.co.il"
When I go to the home page
 And I fill in "reuven@lerner.co.il" for "email_address"
 And I fill in "password" for "password"
 And I press "submit"
Then I should see "Welcome back to the site, Reuven!"

一旦我的两个场景通过,我就将它们提交到版本控制并将其保存在我的应用程序的 features 目录中。

如果我的新场景未通过,我将经历与之前相同的迭代过程——编写步骤定义或修复代码中的错误以确保步骤通过。但是,Cucumber 的执行速度有点慢,因此运行所有功能和所有场景可能会很痛苦。因此,您可以手动运行 Cucumber,而不是通过 Rake

cucumber features/login.feature

您甚至可以指示您只想运行问题文件中第 13 行开始的功能

cucumber features/login.feature:13

当您在一个文件中有很多场景并且您正在尝试调试其中一个场景时,这可以真正节省时间。

关于 Cucumber 的更多信息

Cucumber 是一个经过深思熟虑的系统,具有大量与 Web 开发人员的需求密切对应的功能和能力。首先,Cucumber 中的步骤定义可以使用 RSpec(默认)或 Shoulda,这两个 BDD 测试框架在 Rails 社区中非常流行。

正如我之前提到的,您可以使用 Cucumber 来测试模型和控制器,而不仅仅用于集成测试。但是,我个人的偏好是以这种方式使用 Cucumber,因为它提供了对事物友好的、用户侧的视角,并让您可以像用户一样测试站点。

好吧,它让您可以像用户一样测试站点,但有一个很大的警告:Webrat 是一个很棒的工具,但它不支持 JavaScript。这意味着如果您的站点有大量的 AJAX 和 JavaScript,您将无法通过 Webrat 对其进行测试。有一些方法可以解决这个问题,包括使用 Webrat(例如,通过在您的应用程序中包含文本链接)和使用外部测试系统,例如 Selenium 或 Celerity/Culerity。但我尚未找到一个易于集成、可靠且可以在服务器和我的桌面上运行的系统。

Cucumber 速度慢是一个缺点;在一个大型应用程序上运行所有场景可能需要相当长的时间。一种解决方案是使用 Cucumber 的标记功能,该功能允许您为一个或多个场景提供标记。然后,您可以运行所有带有该标记的场景,跨所有功能。

如果您想避免每次创建或更新(或查找)对象时都访问数据库,您可以将工厂(例如,Factory Girl)与 Cucumber 集成。这可以加快速度,并为您在创建场景和测试应用程序方面提供极大的灵活性。

结论

Cucumber 是一种创新的测试方法,它真的让我越来越喜欢,并且在我在其他地方尚未见过的程度上展示了英语、基于故事的测试的力量。如果您正在使用 Ruby 进行开发,我强烈建议您考虑将 Cucumber 集成到您自己的工作中。

资源

有关 BDD 的“故事”方法的出色介绍,包括功能和场景,请参阅 BDD 社区的领导者之一 Dan North 的这篇博客文章:dannorth.net/whats-in-a-story

Cucumber 的主页是 cukes.info。该页面包含文档、截屏视频以及指向其他资源的指针,以帮助您开始使用 Cucumber 进行测试。

关于 Cucumber 的一个特别好的演示文稿位于:www.slideshare.net/linoj/cucumber-how-i-slice-it-presentation-924254

RSpec 的主页是 rspec.info,它包含安装和配置文档,以及指向其他文档的指针。

Pragmatic Programmers 最近发布了一本书,名为 The RSpec Book,由 RSpec 维护者 David Chelimsky 和许多其他积极参与 RSpec 社区的人员编写。如果您有兴趣使用 Cucumber(或 RSpec),本书是一个极好的起点。

Shoulda 的主页是 thoughtbot.com/projects/shoulda。那里的文档是一个很好的起点,但您可能需要稍微尝试一下才能掌握要领。

Reuven M. Lerner,一位长期的 Web/数据库开发人员和顾问,是西北大学学习科学博士候选人,研究在线学习社区。在芝加哥地区生活四年后,他最近(与妻子和三个孩子)返回了他们在以色列莫迪因的家。

加载 Disqus 评论