【问题标题】:Refactor Massive Cucumber Step Definition重构 Massive Cucumber 步骤定义
【发布时间】:2014-06-03 18:52:35
【问题描述】:

我的团队目前正在使用我们旧的 UI 验收测试脚本并将它们自动化。为此,我们使用了 Jruby、Cucumber 和 Watir-Webdriver。到目前为止,自动化过程进展顺利。我们唯一的问题是我们的步骤定义开始有点失控了。

例如,在我们的大多数场景中是这样的部分:

Given I press the SEARCH_BUTTON
Then I should land on the SEARCH_PAGE

步骤定义如下所示:

Given(/I press the (.*)$/) do |buttonName|
  if buttonName == 'SEARCH_BUTTON'
      eval "$browser.#{$DataHash['home']['searchButton']}.when_present.click"
  elsif buttonName == 'LOGIN_BUTTON'
      eval "$b.#{$DataHash['loginPage']['loginButton']}.click"
  elsif buttonName == 'HOME_BUTTON'
     eval "$b.#{$DataHash['mainPage']['HomeButton']}.click"
  elsif buttonName == 'ADD_PRODUCT_BUTTON'
      #This if else ladder goes on like this for another 300+ lines
  ...
  end
end

$DataHash 变量引用config.yml,它使用哈希来存储我们正在使用的所有不同的网络元素。

config.yml

home:
  searchButton: "link(:id => 'searchBtn')"
  searchTypeSelectBox: "select_list(:name => 'matchType')"
  searchResetButton: "button(:id => 'resetSearch')"
  #rest of the elements on the home page...

loginPage:
  loginButton: "link(:id => 'login')"
  #rest of the elements on the login page...

....

所以$browser.$DataHash['home']['searchButton'].when_present.click 等价于$browser.link(:id => 'searchBtn').when_present.click

我们为用户可以点击的每个按钮使用这个基本步骤定义,此时这个步骤定义类似于 300 多行代码。其中大部分是像上面这样的单行。我们的其他步骤定义也有同样的问题。有没有什么好的方法可以重构我们的步骤定义,使其不那么臃肿,或者至少更容易搜索,同时又不会降低实际步骤的可重用性?

最初,我们认为我们可以根据正在测试的页面在多个文件中拥有相同的步骤定义。所以在searchDefinitions.rb 中会有一个Given(/I press the (.*)$/) do |buttonName| 的步骤定义,它只有在搜索页面上找到的不同按钮。然后在homeDefinitions.rb 中将是相同的步骤定义,但只有主页按钮的代码。基本上打破了跨多个文件的 if-else 阶梯。当然,Cucumber 不允许在多个文件中定义相同的步骤,所以现在我们有点不知所措。

【问题讨论】:

  • 你让我参与了“重构”和“大规模”。我有一个答案,但请在elsif buttonName == 'LOGIN_BUTTON' 之后写下这一行,这样我就可以确定我正在解决所有必要的变化。
  • @DaveSchweisguth 在示例代码中添加了几行以显示问题。希望能更好地理解问题
  • 我正在寻找另一条 eval 行。
  • 啊,明白了。例如添加了一些。它们之间唯一真正的区别是用于查找正确 html 元素的哈希值
  • $DataHash 值是否总是“link(:id => 'something')”?

标签: webdriver refactoring cucumber jruby acceptance-testing


【解决方案1】:

正如您提到的,您可以重复使用步骤,请参阅Reuse Cucumber steps。但是当我尝试这样做时,我个人发现它非常复杂。所以,从我的角度来看,我建议你实现页面对象模式。这个想法是你描述你的页面,甚至一些模块,比如单独的实体,为你提供与它们交互的能力。有关理解概念,请参阅herehere 你可以找到一些例子。假设您的步骤定义会这样

Given(/I press the (.*)$/) do |buttonName|
  @my_home_page.click_search_button
    ...
  end
end

click_search_button 方法封装了您的“阶梯”逻辑,如果搜索按钮尚不存在,则按下登录按钮。

希望它对你有意义。

【讨论】:

    【解决方案2】:

    假设您显示的eval 行中的细微差别无关紧要,请提取变化为常量的哈希值

    BUTTON_KEYS = {
      'search' => %w(home searchButton),
      'login' => %w(loginPage loginButton)
      # ...
    }
    

    并在您的步骤定义中使用它:

    Given(/I press the (.*) button$/) do |button_name|
      keys = BUTTON_KEYS['button_name']
      eval "$browser.#{$DataHash['#{keys[0]}']['#{keys[1]}']}.when_present.click"
    end
    

    现在您的代码行数减少了一半,重复次数也减少了。

    我将步骤正则表达式更改为包含“按钮”,以删除按钮名称中的重复项,并将按钮名称改为小写,就像普通英语一样。无论您是否向非程序员展示您的功能文件,Cucumber 步骤名称都应该像自然语言一样阅读,以便您在阅读它们时可以考虑产品需求而不是实现细节。

    替代建议,如果确实不需要 YAML 中的两级键,则有效:

    你可以像这样重构 YAML

    search button: "link(:id => 'searchBtn')"
    search type select box: "select_list(:name => 'matchType')"
    search reset button: "button(:id => 'resetSearch')"
    # rest of the elements on the home page...
    
    login button: "link(:id => 'login')"
    # rest of the elements on the login page...
    
    # ...
    

    那么你根本不需要哈希,你的步骤可能只是

    Given(/I press the (.*)$/) do |element_name|
      eval "$browser.#{$DataHash['#{element_name}']}.when_present.click"
    end
    

    或者您可以将 YAML 完全转换为哈希(将方法表示为字符串并使用.send 调用它),这样可以防止您犯一些语法错误。

    【讨论】:

    • 这似乎是一个很有前途的想法。我今天会试一试它的进展情况。我还在关于如何使用数据哈希的问题中添加了一些说明
    • 您最终采用了哪种方法?
    • 我们保留 YAML 文件原样并为 if-else 块创建了一个哈希。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多