a) Multi-line Text
在feature文件中,我们可以嵌入多行文本(multi-line string)作为参数,我们需要用一对三个双引号把我们的文本括起来。《The RSpec Book》一书中的示例如下:
feature文件:
1 Scenario: pending implementation
2 Given a file named "example_without_block_spec.rb" with:
3 """
4 describe "an example" do
5 it "has not yet been implemented"
6 end
7 """
8 When I run "spec example_without_block_spec.rb"
9 Then the exit code should be 0
10 And the stdout should include
11 """
12 Pending:
13 an example has not yet been implemented \(Not Yet Implemented\)
14 .\/example_without_block_spec.rb:2
15 Finished in ([\d\.]*) seconds
16 1 example, 0 failures, 1 pending
17 """
在这个scenario中,Given和And(Then)两个步骤中都使用了多行文本字符串作为它们的参数。这可以带给我们很多灵活性,因为我们可以更精确的描述输入或输出数据在一个文件中的显示情况。
关于字符串的缩进,取决于第一个双引号的位置,所以第四行的describle和第6行的end是左对齐,第5行的it会缩进两个空格。
step_definitions:
1 Given /^a file named "([^"]*)" with:$/ do |filename, text|
2 puts "Given:\n#{filename}"
3 puts "Given:\n#{text}"
4 end
5 When /^I run "([^"]*)"$/ do |filename|
6 puts "When:\n#{filename}"
7 end
8 Then /^the exit code should be (\d+)$/ do |number|
9 puts "Then:\n#{number}"
10 end
11 And /^the stdout should include$/ do |text|
12 puts "And:\n#{text}"
13 end
在这个step definitions文件中,我只是简单的把参数打印出来,当然我们也可以在这里做一些复杂的操作或处理。匹配step的正则表达式不关心这些多行文本字符串,它会在在step语句的最后一个字符处结束。
Cucumber会把多行文本作为最后一个块参数传递给step definition。如在上面的step definition文件的第1行中,filename这个块参数的值会由正则式捕捉到,而text会得到多行文本的值。
results:
1 Given:
2 example_without_block_spec.rb
3
4 Given:
5 describe "an example" do
6 it "has not yet been implemented"
7 end
8
9 When:
10 spec example_without_block_spec.rb
11
12 Then:
13 0
14
15 And:
16 Pending:
17 an example has not yet been implemented \(Not Yet Implemented\)
18 .\/example_without_block_spec.rb:2
19 Finished in ([\d\.]*) seconds
20 1 example, 0 failures, 1 pending
这里只是简单的输出了多行文本,大家注意下缩进的问题,只有第6行的it空了两个空格,其它都是顶格。因为其它的每行开头都和第一个双引号在同一垂直位置。
另外一个例子:http://asymmetrical-view.com/2011/06/02/cucumber-gherkin-and-multiline-arguments.html
b) Tables in Steps
Cucumber支持在steps中传递表格数据,这种方式常用于Given和Then这两个步骤中。看《The RSpec Book》中的示例:
feature文件:
1 Feature: test table in steps
2
3 Scenario: three of a kind beats two pair
4 Given a hand with the following cards:
5 | rank | suit |
6 | 2 | H |
7 | 2 | S |
8 | 2 | C |
9 | 4 | D |
10 | A | H |
11 And another hand with the following cards:
12 | rank | suit |
13 | 2 | H |
14 | 2 | S |
15 | 4 | C |
16 | 4 | D |
17 | A | H |
18 Then the first hand should beat the second hand
在这个feature文件中的Given和And(Given)步骤中,定义了两个表格。当cucumber碰到一个step(Given or Then)的下一行是以“|”开头时,cucumber会解析它和它之后的所有以"|"开头行,并把这些单元格里面的数据存储到Cucumber::Ast::Table 对象里面。我们可以使用hashes()方法得到一个哈希数组。
数组里的每一个哈希都使用表格中的第一行作为key,如:
1 [
2 { :rank => '2', :suit => 'H' },
3 { :rank => '2', :suit => 'S' },
4 { :rank => '4', :suit => 'C' },
5 { :rank => '4', :suit => 'D' },
6 { :rank => 'A', :suit => 'H'}
7 ]
和多文本参数一样,cucumber会把Cucumber::Ast::Table 作为最后一个块参数传递给step definition。
step definitions文件:
1 Given /^a hand with the following cards:$/ do |table|
2 # table is a Cucumber::Ast::Table
3 p table
4 p table.hashes
5 end
6 And /^another hand with the following cards:$/ do |table|
7 # table is a Cucumber::Ast::Table
8 table.hashes.each do |value|
9 puts "#{value}"
10 end
11 end
12 Then /^the first hand should beat the second hand$/ do
13 #do nothing
14 end
在这个文件里,做了些简单的操作。在Given中使用P来监视参数table和table.hashes。在And(Given)中则会用each方法输出参数。
results:
1 | rank | suit |
2 | 2 | H |
3 | 2 | S |
4 | 2 | C |
5 | 4 | D |
6 | A | H |
7
8 [{"rank"=>"2", "suit"=>"H"}, {"rank"=>"2", "suit"=>"S"}, {"rank"=>"2", "suit"=>"C"}, {"rank"=>"4", "suit"=>"D"}, {"rank"=>"A", "suit"=>"H"}]
9
10 {"rank"=>"2", "suit"=>"H"},
11
12 {"rank"=>"2", "suit"=>"S"},
13
14 {"rank"=>"4", "suit"=>"C"},
15
16 {"rank"=>"4", "suit"=>"D"},
17
18 {"rank"=>"A", "suit"=>"H"},
看上面的结果:
1.第1-6行是p table的输出结果,跟定义的一样,是一个表格形式的数据集合。
2.第8行是p table.hashes的输出结果,是一个哈希数组。
3.第9-18行是迭代输出哈希数组。
c) Scenario Outlines
这个Scenario Outlines挺有用的。当我们一个feature文件里有多个scenario的时候,而且每个scenario的步骤都差不多,只是测试的数据不同。在这种情况下我们就可以使用Scenario Outlines,定义一个scenario,然后把测试数据参数化就可以了。继续看《The RSpec Book》中的示例:
feature 文件:
1 Feature: test scenario outline
2
3 Scenario: test one
4 Given the secret code is 1234
5 When I guess 1234
6 Then the mark should be bbbb
7
8 Scenario: test two
9 Given the secret code is 1234
10 When I guess 1235
11 Then the mark should be bbb
12
13 Scenario: test three
14 Given the secret code is 1234
15 When I guess 1236
16 Then the mark should be bbb
在这个feature文件里面,我们有三个scenario(可能会有更多),它们的区别仅仅是测试数据不同。这样写有很多缺点,比如不易读,违反DRY原则等等。
Scenario outliness可以帮我们解决这个问题,让我们只定义一次scenario,然后使用占位符来代替可能不断在改就的值。然后我们可以把那些变化的值以一样表格的形式组织起来。
改进后的feature文件
1 Scenario Outline: submit guess
2 Given the secret code is "<code>"
3 When I guess "<guess>"
4 Then the mark should be "<mark>"
5
6 Scenarios: all numbers correct
7 | code | guess | mark |
8 | 1234 | 1234 | ++++ |
9 | 1234 | 1243 | ++-- |
10 | 1234 | 1423 | +--- |
11 | 1234 | 4321 | ----|
这样编写feature文件,使得我们很容易阅读,同时也给我们了一个大纲,让我们清晰的知道这个scenario的什么用。
关键字Scenarios(Examples)定义了一个输入数据的table,这样cucumber会处理除开第一行(在这是第7行)的每第行数据,
此例中总共执行四次, 所以实际上会有四个scenario。
这种参数化的方式也可以使用在multi-line text 和 table in steps中,继续看示例:
feature文件:
1 Feature: Test scenario outline
2
3 Scenario Outline:
4 Given a discount of <discount>
5 When I order the following book:
6 | title | price |
7 | Healthy eating for programmers | <price> |
8 Then the statement should read:
9 """
10 Statement for David
11 Total due: <total>
12 """
13
14 Scenarios:
15 | discount | price | total |
16 | 10% | $29.99 | $26.99 |
17 #| 15% | $29.99 | $25.49|
在这个文件中,Given,When和Then分别使用了三种不同的方式传递参数,但都使用的参数化:
Given:参数化
When:table in steps + 参数化
Then: multi-line text + 参数化
step definition文件:
1 Given /^a discount of (\d+)%$/ do |discount|
2 p discount
3 end
4
5 When /^I order the following book:$/ do |table|
6 # table is a Cucumber::Ast::Table
7 p table
8 end
9 Then /^the statement should read:$/ do |string|
10 p string
11 end
在该文件中,只是简单的使用p输出了参数,结果如下。
results:
1 "10"
2
3 | title | price |
4 | Healthy eating for programmers | $29.99 |
5
6 "Statement for David\nTotal due: $26.99"
占位符的地方被实际值代替后,得到上面的结果。
注:本文大部分内容来自《The RSpec Book》一书,仅共学习之用,如需要了解更多内容,请看原文。