We want to be able to commit our code frequently to prevent merge headaches.
“the longer you wait, the more your code will diverge from your teammates. If you don’t commit often you rob them of the opportunity to reduce merge hell.” Aslak Hellesøy
When dealing with Cucumber and Features/Scenarios we may find we want to commit part way through a scenario but we won’t because:
We don’t want to break the build
We don’t want to pollute the build with lots of pending steps.
A common solution to this problem is to create two streams for running the features:
In-progress
If an in-progress scenario fails then the build carries on.
If an in-progress scenario passes then the build fails (This is very similar to how Rspec works with pending)
Finished
If a completed scenario fails it causes the build to fail.
We can implement this model using Cucumber’s new Tag feature. We can tag Scenarios and Features with @in-progress and use this tag to help exclude in-progress Features/Scenarios from the finished build.
@in-progress
Feature:
In order to avoid merge headaches
As a developer
I want to tag my features and scenarios with a in-progress tag
@in-progress
Scenario: I'm not finished yet
Given ...
When ...
Then ...
The Rake tasks
Finished features/scenarios task
We prefix tags with ~ to exclude features or scenarios having that tag
We require a special formatter Cucumber::Formatter::InProgress which is essential for making the task work. This formatter as well as giving helpful output changes the command line exit codes of Cucumber. This is kind of crazy but only within the formatter do we have enough information to decided if we should fail or pass. The formatter only returns a failure exit code if there were any scenarios which passed. So unlike the default exit codes failing steps will not cause a failure exit code.
require'cucumber/rake/task'classBuildFailure<Exception;definitialize(message=nil)message||="Build failed"super(message)endend;Cucumber::Rake::Task.newdo|t|t.cucumber_opts="--format progress"endnamespace:featuresdodesc"Run finished features"Cucumber::Rake::Task.new(:finished)do|t|t.cucumber_opts="--format progress --tags ~in-progress"enddesc"Run in-progress features"Cucumber::Rake::Task.new(:in_progress)do|t|t.cucumber_opts="--require formatters/ --format Cucumber::Formatter::InProgress --tags in-progress"endenddesc"Run complete feature build"task:cruisedofinished_successful=run_and_check_for_exception("finished")in_progress_successful=run_and_check_for_exception("in_progress")unlessfinished_successful&&in_progress_successfulputsputs("Finished features had failing steps")unlessfinished_successfulputs("In-progress Scenario/s passed when they should fail or be pending")unlessin_progress_successfulputsraiseBuildFailureendenddefrun_and_check_for_exception(task_name)puts"*** Running #{task_name} features ***"beginRake::Task["features:#{task_name}"].invokerescueException=>ereturnfalseendtrueend
moduleCucumbermoduleFormatterclassInProgress<ProgressFAILURE_CODE=1SUCCESS_CODE=0FORMATS[:invalid_pass]=Proc.new{|string|::Term::ANSIColor.blue(string)}definitialize(step_mother,io,options)super(step_mother,io,options)@scenario_passed=true@passing_scenarios=[]@feature_element_count=0enddefvisit_feature_element(feature_element)super@passing_scenarios<<feature_elementif@scenario_passed@scenario_passed=true@feature_element_count+=1@io.flushenddefvisit_exception(exception,status)@scenario_passed=falsesuperendprivatedefprint_summaryunless@passing_scenarios.empty?@io.putsformat_string("(::) Scenarios passing which should be failing or pending (::)",:invalid_pass)@io.puts@passing_scenarios.eachdo|element|@io.puts(format_string(element.backtrace_line,:invalid_pass))end@io.putsendprint_countsunless@passing_scenarios.empty?override_exit_code(FAILURE_CODE)elseoverride_exit_code(SUCCESS_CODE)endenddefoverride_exit_code(status_code)at_exitdoKernel.exit(status_code)endendendendend