您的位置:首页 > 其它

故事驱动开发实践-一次完整的使用过程

2012-06-29 15:04 531 查看
本文的例子参考 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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐