×

使用 cucumber 编写用户故事

96
编程青年
2016.02.16 09:22* 字数 784

cucumber 是一款 BDD 的工具,通常用于集成测试。
如果应用是一个只提供 API 给手机端调用的后端程序。本来并不是很适合使用 cucumber。
不过随着开发的深入,我越来越觉得单纯的 API 测试(controller 层)无法表达用户的意图。
而目在开发团队对产品的用例也不是非常清楚,没有文档可言的情况下,使用 cucumber 来编写用例也是一个不错的选择。

使用 cucumber 的好处

在开发应用时,我们会把大的功能点分解成小的功能。但是在理解业务逻辑时,我们要从更宏观的角度看待系统。
Rails 框架的单体风格并不足以让人非常舒服的熟悉一个系统。因为 ActiveRecord 的特性,我们只能看到模型之间的关系,而看不到用户是是如何和它们交互的。
这就像给你一堆齿轮,转轴和马达一样,你知道如何拼装它们,但你看不到它们所组成的到底是汽车,飞机还是轮船。
通过用户故事,无论是开发人员还是业务人员都能对系统有一个快速直观的理解。
而 cucumber 就是将用户故事代码化的一款工具。

Get Started

如果使用 rails 自带的 minitest 测试框架。添加 cucumber 的步骤如下。

  1. 在 Gemfile 中添加

    group :test, :development do
      gem 'cucumber-rails', :require => false
      gem 'database_cleaner'
    end
    
  2. 安装

    bundle install
    rails generate cucumber:install
    
  3. 编写第一个 feature

    @require_login
    Feature: XiaomiSports
      In order to know about my xiaomi sports
      As a care user with xiaomi profile binding
      I want list of my xiaomi sports
    
    Scenario: List of the xiaomi sports
      Given the system knows my xiaomi profile
      And the system knows about the following sports::
        |  record_on |  step |
        | 2015-05-25 |  8000 |
        | 2015-05-26 | 10000 |
        | 2015-05-27 | 12000 |
      When the client requests GET "/api/v1/xiaomi_profile/exercise_data?fromDate=2015-05-25&toDate=2015-05-27"
      Then response should be "200"
      And the JSON response should be an array with 3 "step" elements
    
  4. 执行 bin/rake cucumber 并且实现相应步骤
    cucumber 会自动输出实现步骤的提示代码类似于:

You can implement step definitions for undefined steps with these snippets:
Given(/^the system knows about the processions of my corp$/) do
     pending # express the regexp above with the code you wish you had
end

只要把上述代码拷贝到 features/step_definitions/xxxx_steps.rb中, 然后填写测试代码, 那么执行到这段"情节"时, 代码块中的代码就会被执行.

tips

一些实用小技巧

使用 tag 来实现 AOP 的效果

我在上面的代码中使用了 @require_login 这个 tag, 表示下面的步骤是需要在用户登录状态下才能完成的.

可以对 tag 添加钩子函数来 DRY,features/support/hooks.rb

Before('@require_login') do
  # login logic
end

table 的使用

在 cucumber 中使用 table 可以非常直观的表达一组数据结构, 比如上面提到的:

|  record_on |  step |
| 2015-05-25 |  8000 |
| 2015-05-26 | 10000 |
| 2015-05-27 | 12000 |

在 step 中可以这样获取:

Given(/^the system knows about the following sports::$/) do |table|
    table.hashes #=> [{"record_on"=>"2015-05-25", "step"=>"8000"}, {"record_on"=>"2015-05-26", "step"=>"10000"}, {"record_on"=>"2015-05-27", "step"=>"12000"}]
end

request header 的设置方式

直接上代码, 模拟客户端的请求

  @current_user = FactoryGirl.create(:access_token).user
  header 'Accept', 'application/json'
  header 'Content-Type', 'application/json'
  header 'Token', @current_user.access_tokens.last.token

参考文档

https://cucumber.io/docs/reference/rails
http://anthonyeden.com/2013/07/10/testing-rest-apis-with-cucumber-and-rack.html
http://www.emilsoman.com/blog/2013/05/18/building-a-tested/

posts
Web note ad 1