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

本文的例子参考 The Cucumber Book

以前一直做单元测试,虽然不够彻底,但是一直有些坚持。在实践单元测试的过程中,总是会有一种感觉,无法从需要和总体上做程序,流程总是不顺畅。也许是我实践的办法不对吧。总是无法将测试驱动开发进行到底。在追赶的项目进度面前,早点搞定,成了我的座右铭。但是项目快结束后,看着自己的代码,我总有一种欲哭无泪的感觉,总想重构代码。但不敢下手,也无从下手。谁叫我的测试代码不够彻底呢。怕修改代码后,会带来新的bug。 现在实践一下故事驱动开发。其实对故事驱动开发非常有兴趣。因为我特别喜欢故事面板,一个项目,在我看来,无非就是一个一个的故事组成的。


  1. 工具
    rails3.2
    cucumber

  2. 新建项目
    rails new squeaker --skip-test-unit


  3. 修改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

  4. create cucumber files for rails
    rails generate cucumber:install

  5. 建立ctags索引
    ctags -R --exclude=*.js --exclude=.log * . ~/.rvm/gems/ruby-1.9.2-p320/gems/

  6. 编写故事用例
    Feature: 查看消息
    
      Scenario: 查看其他用户的消息
        Given 有一个注册用户A 
        And 用户A提交了一条信息 "这是我的消息" 
        When 我访问了该用户的页面
        Then 我应该可以看到 "这是我的消息"

  7. 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
    



  8. 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
    

  9. 新建两个model对象
    rails g model user username:string
    rails g model Message user_id:integer content:string

    rake db:migrate db:test:prepare
    

  10. controller 和view略

    整体结构把握好了,其他的就好做了

    Given : context 运行的上下文环境
    When : event 事件
    Then:  结果 should


  11. 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


  12. 使用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
    



  13. 资源地址:
    cucumber rails :https://github.com/cucumber/cucumber-rails
    cucumber: https://github.com/cucumber/cucumber

  14. 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.
    
    




你可能感兴趣的:(故事驱动开发实践-一次完整的使用过程)