本文的例子参考 The Cucumber Book
以前一直做单元测试,虽然不够彻底,但是一直有些坚持。在实践单元测试的过程中,总是会有一种感觉,无法从需要和总体上做程序,流程总是不顺畅。也许是我实践的办法不对吧。总是无法将测试驱动开发进行到底。在追赶的项目进度面前,早点搞定,成了我的座右铭。但是项目快结束后,看着自己的代码,我总有一种欲哭无泪的感觉,总想重构代码。但不敢下手,也无从下手。谁叫我的测试代码不够彻底呢。怕修改代码后,会带来新的bug。 现在实践一下故事驱动开发。其实对故事驱动开发非常有兴趣。因为我特别喜欢故事面板,一个项目,在我看来,无非就是一个一个的故事组成的。
- 工具
rails3.2
cucumber
- 新建项目
rails new squeaker --skip-test-unit
- 修改Gemfile文件
group :test do gem 'cucumber-rails', '1.3.0' gem 'database_cleaner', '0.8.0' gem 'rspec-rails', '2.10.0' gem 'factory_girl', '3.5.0' end
bundle install
- create cucumber files for rails
rails generate cucumber:install
- 建立ctags索引
ctags -R --exclude=*.js --exclude=.log * . ~/.rvm/gems/ruby-1.9.2-p320/gems/
- 编写故事用例
Feature: 查看消息 Scenario: 查看其他用户的消息 Given 有一个注册用户A And 用户A提交了一条信息 "这是我的消息" When 我访问了该用户的页面 Then 我应该可以看到 "这是我的消息"
- steps 源代码
# -*- encoding: utf-8 -*- Given /^有一个注册用户A$/ do Factory(:user) end Given /^用户A提交了一条信息 "(.*?)"$/ do |arg1| User.count.should == 1 Factory(:message, :content => arg1, :user => User.first) end When /^我访问了该用户的页面$/ do User.count.should == 1 visit(user_path(User.first)) end Then /^我应该可以看到 "(.*?)"$/ do |text| page.should have_content(text) end
- factory_girl的支持
require 'factory_girl' FactoryGirl.define do factory :user do |f| f.username 'testuser' end factory :message do |f| f.association :user f.content 'Test message content' end end
- 新建两个model对象
rails g model user username:string rails g model Message user_id:integer content:string
rake db:migrate db:test:prepare
- controller 和view略
整体结构把握好了,其他的就好做了
Given : context 运行的上下文环境
When : event 事件
Then: 结果 should
- watchr监控 cucumber的修改
# This usually sits at the root of the project and is called ".watchr" @spec_cmd = "bundle exec rspec" @cuc_cmd = "bundle exec cucumber" # Convenience methods ######################################################## # Working on eventually adding Growl support. The problem right now is catching # the output of a command, while at the same time piping it out to the stdout. # Right now I don't know how to do it without stripping the ANSI color tags. # def growl(message) # if message.match /(\d+)\s(errors?|failures?)/ # Rspec failures # Growl.notify "#{$1} spec #{$2}", :title => 'Watchr: specs failing' # elsif message.match /(\d+)\sfailed/ # Cucumber failures # Growl.notify "#{$1} features failed", :title => 'Watchr: features failing' # end # end def run(command) puts "\n\n" puts command system command puts "\n\n" @interrupted = false end def run_all_specs result = run "#{@spec_cmd} spec/" end def run_spec(spec) result = run "#{@spec_cmd} #{spec}" end def related_specs(path) Dir['spec/**/*.rb'].select do |file| file =~ /#{File.basename(path).split('.').first}_spec.rb/ end end def run_all_features result = run @cuc_cmd end def run_feature(feature) result = run "#{@cuc_cmd} #{feature}" end def run_suite run_all_specs run_all_features end # Watchr rules ############################################################### watch('spec/spec_helper\.rb') { run_all_specs } watch('spec/support/.*') { run_all_specs } watch('spec/.*_spec\.rb') { |m| run_spec m[0] } watch('app/.*\.rb') { |m| related_specs(m[0]).map { |s| run_spec s } } watch('lib/.*\.rb') { |m| related_specs(m[0]).map { |s| run_spec s } } watch('features/support/.*') { |m| run_all_features } watch('features/.*\.feature') { |m| run_feature m[0] } watch('features/step_definitions/(.*)\.rb') { |m| run_feature "#{m[1]}.feature" } #有一个要求,就是step定义名称跟feature名称一样 # Signals #################################################################### @interrupted = false Signal.trap 'QUIT' do # CTRL-\ puts " --- Running all specs ---\n\n" run_all_specs end Signal.trap 'INT' do # CTRL-C if @interrupted abort "\n" else puts 'Interrupt a second time to quit' puts " --- Running entire test suite ---\n\n" @interrupted = true sleep 1.5 run_suite end end
gist地址是:
https://gist.github.com/3108848
一般会在lib/tasks/watchr.rake文件
desc "Run Watchr" task :watchr do sh %{bundle exec watchr .watchr} end
- 使用spork加速cucumber
修改env.rb文件
require 'spork' Spork.prefork do ENV["RAILS_ENV"] ||= "test" require File.expand_path(File.dirname(__FILE__) + '/../../config/environment') require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support require 'cucumber/rails' require 'capybara/rails' require 'capybara/cucumber' require 'capybara/session' # Lets you click links with onclick javascript handlers without using @culerity or @javascript # Commented out because it causes bugs :-\ #require 'cucumber/rails/capybara_javascript_emulation' Capybara.default_selector = :css Cucumber::Rails::Database.javascript_strategy = :truncation end Spork.each_run do ActionController::Base.allow_rescue = false #Cucumber::Rails::World.use_transactional_fixtures = true if defined?(ActiveRecord::Base) begin require 'database_cleaner' DatabaseCleaner.strategy = :truncation rescue LoadError => ignore_if_database_cleaner_not_present end end end
- 资源地址:
cucumber rails :https://github.com/cucumber/cucumber-rails
cucumber: https://github.com/cucumber/cucumber
- factory ,cucumber整合的问题
You should only need these steps. Gemfile: group :development, :test do gem "rspec-rails" end group :test do gem "cucumber-rails" gem "factory_girl_rails" end features/support/factory_girl.rb: require 'factory_girl/step_definitions' spec/factories.rb: # your Factory definitions.