BDD Ten tips for writing better Cucumber steps

恒温 · 2013年11月04日 · 最后由 xueyan 回复于 2019年11月04日 · 2431 次阅读
本帖已被设为精华帖!

原文:http://coryschires.com/ten-tips-for-writing-better-cucumber-steps/

灵活使用单复数

紧接着在以 s 结尾的复数后面加一个?,比如

Then /^the users? should receive an email$/ do
# ...
end

这个 ? 其实就是正则表达式的问号,可以表示重复前面内容的 0 次或一次,也就要么不出现,要么出现一次。所以上面的例子可以匹配 userusers

使用不必被捕获的元组,可以使 steps 读起来更自然

在元组前面加 ?:, 比如: (?:some text)。 匹配 some text, 但不捕获匹配的文本,也不给此分组分配组号。这表示这个括号里的东西不会被当作参数传递给你的 step definition。 比如

And /^once the files? (?:have|has) finished processing$/ do
# ...
end

这可以匹配:

once the file have finished processing (不合理)
once the files have finished processing
once the file has finished processing
once the files has finished processing (不合理)

或者

When /^(?:I|they) create a profile$/ do
# ...
end

这可以匹配:

I create a profile
they create a profile

捕获一个选择性的元组来合并 step definition

我们常常会用到 或者 不是 的判断。 比如

I should see the message
I should not see the message

那其实我们可以把 not 做为一个供选择的元组,合并这两个 step。比如:

Then /^I should( not)? see the following columns: "([^"]*)"$/ do |negate, columns|
  within('table thead tr') do
    columns.split(', ').each do |column|
      negate ? page.should_not(have_content(column)) : page.should(have_content(column))
    end
  end
end

不要把所有的要匹配的短语都用括号括起来

常常看到这样的 step:

Given I have "potatoes" in my cart

这个括号很形象直观,突出了 potatoes 是我要传递的参数。但是:

Given I have "7" items in my cart

这样就很糟糕了。数字类型的还是建议不需要用括号括起来。

使用 transforms 写出更加聪明干净的正则表达式

对于数字的处理,我们一般会这样:

Then /^I have (\d+) items? in my cart$/ do |item_count|
  item_count.to_i.times { ... }
end

Cucumber 把任何东西当成字符串传递。所以你必须谨记,在传递数字并进行比较的时候,要用 to_i

但是,自己来转就会很容易忘记。所以 Cucumber 提供了 Transform

CAPTURE_A_NUMBER = Transform /^\d+$/ do |number|
  number.to_i
end
Then /^I have (#{CAPTURE_A_NUMBER}) items? in my cart$/ do |item_count|
  item_count.times { ... }
end

这样我们就不用主动的调用 to_i 方法,而且因为 CAPTURE_A_NUMBER 的抽离,我们也更好维护这段匹配。

定义方法来压缩 step definition

去冗的一个方法就是把重复的步骤或者重复的定义变成一个方法。比如,得到当前用户:

def current_user
  User.find_by_email('current_user@example.com')
end

或者把有想通逻辑的步骤包装起来。比如:

def within_lightbox(opts = {sleep: 0} )
  sleep(opts[:sleep])
  within_frame("prettyPhotoIframe") { yield }
end

这样我们的 step 定义就可以干净优美:

Then /^some stuff should be visible in the lightbox$/ do
  within_lightbox { page.should have_content('Some Stuff') }
end

我们可以把这些方法加入到 features/support/[whatever].rb 里去, cucumber 在运行的时候,会把这些方法读入到它的 world 中去。

当然,有得必有失,你封装了逻辑,也就使得步骤不是那么直观了,想要明白它在做什么,就必须到你定义的方法里去找。所以利弊要自己权衡。

在 step 里面使用 step

比如:


When /^the request for an expedited decision should be canceled$/ do
  manuscript.should_not be_expedited
  step %{"#{current_user.email}" should receive an email with subject "Expedited decision request canceled"}
end

但也不要乱用,比如:

When /^I update the email template to read "([^"]*)"$/ do |text|
  fill_in("email_template[text], with: text)
  click_button("Save changes and close")
  # Rather than...
  # step %{I fill in "email_template[text]" with "#{text}"}
  # step %{I press "Save changes and close"}
end

不需要一定要使用 ^ 或者 $ 来匹配

通常我们会这样写:

Given /^I am an admin user$/ do |item_count|
  # ...
end

我们使用^ 或者 $ 来固定我们要匹配的东西。上面的例子就是完全匹配 I am an admin user, 多一个少一个都不行。大多数情况下,这样很好。但是:

Then /^wait (\d+) seconds/ do |seconds|
  sleep(seconds.to_i)
end

有时候,其实不必要固定结尾。这样,上面的语句就可以匹配:

Then wait 2 seconds for the revenue statistics to finish loading
Then wait 5 seconds while the document is converted

尽量使用 table 来包含你的数据

一旦,测试数据很大,就请使用 table,否则等待你的就是重复的步骤。

Given "Frankie's Hams" are selling for $25:
And the following orders have been placed:
| buyer email      | quantity |
| eddy@example.com | 3        |
| matt@example.com | 2        |

使用小技巧可以,但是不要得不偿失

Cucumber 有很多小技巧,但是请记住,你去掉了重复,就必然增加了复杂。比如

Given /^(?:I|they) have opened (?:a|an) ([^\s]+) invitation$/ do |invitation_type|
invitation = Factory("#{invitation_type}_invitation".to_sym, recipient: current_user, to: current_user.email)
reset_mailer
eval "ApplicationMailer.invite_#{invitation_type}(invitation).deliver"
open_email(current_user.email)
end

上面的语句可以匹配:

Given I have opened a reviewer invitation
Given I have opened an editor invitation

但是这个表达式实在过于复杂,理解起来也很困难,不如拆开成两个更好。


这篇文章写的不错,在我在公司项目实践中都运用到了。不过不得不说一句,测试工具再好,也需要开发的配合。如果代码本身就复杂或者重复实现,那你也没办法。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 9 条回复 时间 点赞

今天把 testerhome 精华帖转了一圈 发现如此好文 赞!

#1 楼 @55hhy 你们也开始用了?

一年前在 red hat 的时候,我们也是用的 cucumber 😄

#4 楼 @xiaozi0lei 黄瓜系列还是很不错的。

#5 楼 @lihuazhang BDD 的思想很前卫,如果真的习惯了这种开发方式,软件质量将有巨大的提升。 😄

#6 楼 @xiaozi0lei 我倒是没有体会那么深,我当时觉得 build the right thing 对我触动很大。

#7 楼 @lihuazhang 恩啊,这种模式可以更好的保证我们 build 到最后是 the right thing,这其实应了那句 “bug 越早发现,修正的代价越小” 😄

现在还推荐使用吗

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册