使用 cucumber 编写用户故事
cucumber 是一款 BDD 的工具,通常用于集成测试。
如果应用是一个只提供 API 给手机端调用的后端程序。本来并不是很适合使用 cucumber。
不过随着开发的深入,我越来越觉得单纯的 API 测试(controller 层)无法表达用户的意图。
而目在开发团队对产品的用例也不是非常清楚,没有文档可言的情况下,使用 cucumber 来编写用例也是一个不错的选择。
使用 cucumber 的好处
在开发应用时,我们会把大的功能点分解成小的功能。但是在理解业务逻辑时,我们要从更宏观的角度看待系统。
Rails 框架的单体风格并不足以让人非常舒服的熟悉一个系统。因为 ActiveRecord 的特性,我们只能看到模型之间的关系,而看不到用户是是如何和它们交互的。
这就像给你一堆齿轮,转轴和马达一样,你知道如何拼装它们,但你看不到它们所组成的到底是汽车,飞机还是轮船。
通过用户故事,无论是开发人员还是业务人员都能对系统有一个快速直观的理解。
而 cucumber 就是将用户故事代码化的一款工具。
Get Started
如果使用 rails 自带的 minitest 测试框架。添加 cucumber 的步骤如下。
-
在 Gemfile 中添加
group :test, :development do gem 'cucumber-rails', :require => false gem 'database_cleaner' end
-
安装
bundle install rails generate cucumber:install
-
编写第一个 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
-
执行
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/