From baa998ad45f7bcf0a353db8786c997664aa434a9 Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Fri, 14 Oct 2016 11:48:06 -0600 Subject: [PATCH 1/9] Only create test cases for scenario outline rows If a row doesn't respond to scenario_outline, assume it is a data table rather than an example table for a scenario outline. It doesn't make sense to create a test case for a row in a data table. --- lib/ci/reporter/cucumber.rb | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/ci/reporter/cucumber.rb b/lib/ci/reporter/cucumber.rb index 42a82ab..e5a7d67 100644 --- a/lib/ci/reporter/cucumber.rb +++ b/lib/ci/reporter/cucumber.rb @@ -110,11 +110,14 @@ def after_examples(*args) def before_table_row(table_row) row = table_row # shorthand for table_row # check multiple versions of the row and try to find the best fit - outline = (row.respond_to? :name) ? row.name : - (row.respond_to? :scenario_outline) ? row.scenario_outline : - row.to_s - @test_case = TestCase.new("#@scenario (outline: #{outline})") - @test_case.start + + if row.respond_to? :scenario_outline + outline = (row.respond_to? :name) ? row.name : + (row.respond_to? :scenario_outline) ? row.scenario_outline : + row.to_s + @test_case = TestCase.new("#@scenario (outline: #{outline})") + @test_case.start + end end def after_table_row(table_row) @@ -122,11 +125,14 @@ def after_table_row(table_row) @header_row = false return end - @test_case.finish - if table_row.respond_to? :failed? - @test_case.failures << CucumberFailure.new(table_row) if table_row.failed? - test_suite.testcases << @test_case - @test_case = nil + + if table_row.respond_to?(:scenario_outline) + @test_case.finish + if table_row.respond_to? :failed? + @test_case.failures << CucumberFailure.new(table_row) if table_row.failed? + test_suite.testcases << @test_case + @test_case = nil + end end end end From 419d8211d4526e143ce82351fdc79b758a3070aa Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Tue, 18 Oct 2016 15:26:54 -0600 Subject: [PATCH 2/9] Don't create a test case for a header row --- lib/ci/reporter/cucumber.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ci/reporter/cucumber.rb b/lib/ci/reporter/cucumber.rb index e5a7d67..9081e2b 100644 --- a/lib/ci/reporter/cucumber.rb +++ b/lib/ci/reporter/cucumber.rb @@ -111,7 +111,7 @@ def before_table_row(table_row) row = table_row # shorthand for table_row # check multiple versions of the row and try to find the best fit - if row.respond_to? :scenario_outline + if row.respond_to?(:scenario_outline) && !@header_row outline = (row.respond_to? :name) ? row.name : (row.respond_to? :scenario_outline) ? row.scenario_outline : row.to_s From 49e9e940513f619bf0adca4d86f365c2a178c4a0 Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Tue, 18 Oct 2016 15:30:16 -0600 Subject: [PATCH 3/9] Simplify naming of test cases from example rows --- lib/ci/reporter/cucumber.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/ci/reporter/cucumber.rb b/lib/ci/reporter/cucumber.rb index 9081e2b..7bf8a1b 100644 --- a/lib/ci/reporter/cucumber.rb +++ b/lib/ci/reporter/cucumber.rb @@ -112,10 +112,7 @@ def before_table_row(table_row) # check multiple versions of the row and try to find the best fit if row.respond_to?(:scenario_outline) && !@header_row - outline = (row.respond_to? :name) ? row.name : - (row.respond_to? :scenario_outline) ? row.scenario_outline : - row.to_s - @test_case = TestCase.new("#@scenario (outline: #{outline})") + @test_case = TestCase.new("#@scenario (outline: #{row.name})") @test_case.start end end From 5e84cdb0c66445a516f753df923bdd5931c4a387 Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Tue, 18 Oct 2016 15:31:31 -0600 Subject: [PATCH 4/9] Remove extraneous local variable 'row' --- lib/ci/reporter/cucumber.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/ci/reporter/cucumber.rb b/lib/ci/reporter/cucumber.rb index 7bf8a1b..d3187ac 100644 --- a/lib/ci/reporter/cucumber.rb +++ b/lib/ci/reporter/cucumber.rb @@ -108,11 +108,8 @@ def after_examples(*args) end def before_table_row(table_row) - row = table_row # shorthand for table_row - # check multiple versions of the row and try to find the best fit - - if row.respond_to?(:scenario_outline) && !@header_row - @test_case = TestCase.new("#@scenario (outline: #{row.name})") + if table_row.respond_to?(:scenario_outline) && !@header_row + @test_case = TestCase.new("#@scenario (outline: #{table_row.name})") @test_case.start end end From a703cbaa747c8ef41a9f1b2e01410aad309f7b55 Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Tue, 18 Oct 2016 15:42:38 -0600 Subject: [PATCH 5/9] Add unit tests for table rows in scenario outlines --- spec/ci/reporter/cucumber_spec.rb | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/spec/ci/reporter/cucumber_spec.rb b/spec/ci/reporter/cucumber_spec.rb index 3e95d61..5ebb459 100644 --- a/spec/ci/reporter/cucumber_spec.rb +++ b/spec/ci/reporter/cucumber_spec.rb @@ -218,5 +218,47 @@ def new_instance end end end + + context "inside a scenario outline" do + let(:testcases) { [] } + let(:test_suite) { double("test_suite", testcases: testcases) } + let(:cucumber) { new_instance } + let(:test_case) { double("test_case", start: nil, finish: nil, name: "Step Name") } + let(:scenario_outline) { double("scenario_outline") } + let(:step_collection) { double("step_collection") } + + before :each do + allow(cucumber).to receive(:test_suite).and_return(test_suite) + allow(CI::Reporter::TestCase).to receive(:new).and_return(test_case) + + cucumber.scenario_name(nil, "Scenario Name") + end + + context "processing a data table" do + let(:table_row) { double("table_row", name: "Table Row Name") } + + describe "before table row" do + it "does not create a new test case" do + expect(CI::Reporter::TestCase).to_not receive(:new) + cucumber.before_table_row(table_row) + end + end + end + + context "processing an examples table" do + let(:table_row) do + double("table_row", name: "Table Row Name", scenario_outline: scenario_outline) + end + let(:expected_test_case_name) { "Scenario Name (outline: Table Row Name)" } + + describe "before table row" do + it "creates a new test case" do + expect(CI::Reporter::TestCase).to receive(:new).with(expected_test_case_name) + cucumber.before_table_row(table_row) + end + end + end + end + end end From 67c88d16ab1d06884a68050acd23c7dd559da9e0 Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Tue, 18 Oct 2016 15:43:42 -0600 Subject: [PATCH 6/9] Handle before/after feature_element messages --- lib/ci/reporter/cucumber.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/ci/reporter/cucumber.rb b/lib/ci/reporter/cucumber.rb index d3187ac..1783e15 100644 --- a/lib/ci/reporter/cucumber.rb +++ b/lib/ci/reporter/cucumber.rb @@ -65,6 +65,14 @@ def feature_name(keyword, name) @name = (name || "Unnamed feature").split("\n").first end + def before_feature_element(feature_element) + @feature_element = feature_element + end + + def after_feature_element(feature_element) + @feature_element = nil + end + def scenario_name(keyword, name, *args) @scenario = (name || "Unnamed scenario").split("\n").first end From 876c1283f1bf9654f8728bdf3635173d97781280 Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Tue, 18 Oct 2016 15:46:17 -0600 Subject: [PATCH 7/9] Add feature_element_type method The main reason for this addition is to consolidate calls to instance_of? into a method that can be easily stubbed, since mock objects won't play nicely with instance_of? --- lib/ci/reporter/cucumber.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/ci/reporter/cucumber.rb b/lib/ci/reporter/cucumber.rb index 1783e15..63125c4 100644 --- a/lib/ci/reporter/cucumber.rb +++ b/lib/ci/reporter/cucumber.rb @@ -73,6 +73,16 @@ def after_feature_element(feature_element) @feature_element = nil end + def feature_element_type + if @feature_element.instance_of?(::Cucumber::Ast::Scenario) + return :scenario + elsif @feature_element.instance_of?(::Cucumber::Ast::ScenarioOutline) + return :scenario_outline + else + return :unknown + end + end + def scenario_name(keyword, name, *args) @scenario = (name || "Unnamed scenario").split("\n").first end From c4b9404f42dafa6c89050af93dfd8a18b5476ab2 Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Tue, 18 Oct 2016 15:48:55 -0600 Subject: [PATCH 8/9] Don't create TestCase for steps in ScenarioOutline Only create a TestCase if we are in a Scenario. Steps in a ScenarioOutline don't by themselves define a TestCase to be executed, timed, and reported. Not until we get to the example rows will we want to do that. --- lib/ci/reporter/cucumber.rb | 42 +++++++++++++++++-------------- spec/ci/reporter/cucumber_spec.rb | 22 ++++++++++++++++ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/lib/ci/reporter/cucumber.rb b/lib/ci/reporter/cucumber.rb index 63125c4..6b2b2f8 100644 --- a/lib/ci/reporter/cucumber.rb +++ b/lib/ci/reporter/cucumber.rb @@ -88,8 +88,10 @@ def scenario_name(keyword, name, *args) end def before_steps(steps) - @test_case = TestCase.new(@scenario) - @test_case.start + if feature_element_type == :scenario + @test_case = TestCase.new(@scenario) + @test_case.start + end end def treat_pending_as_failure? @@ -97,25 +99,27 @@ def treat_pending_as_failure? end def after_steps(steps) - @test_case.finish - - case steps.status - when :pending, :undefined - if treat_pending_as_failure? - @test_case.failures << CucumberFailure.new(steps) - else - @test_case.name = "#{@test_case.name} (PENDING)" - @test_case.skipped = true + if feature_element_type == :scenario + @test_case.finish + + case steps.status + when :pending, :undefined + if treat_pending_as_failure? + @test_case.failures << CucumberFailure.new(steps) + else + @test_case.name = "#{@test_case.name} (PENDING)" + @test_case.skipped = true + end + when :skipped + @test_case.name = "#{@test_case.name} (SKIPPED)" + @test_case.skipped = true + when :failed + @test_case.failures << CucumberFailure.new(steps) end - when :skipped - @test_case.name = "#{@test_case.name} (SKIPPED)" - @test_case.skipped = true - when :failed - @test_case.failures << CucumberFailure.new(steps) - end - test_suite.testcases << @test_case - @test_case = nil + test_suite.testcases << @test_case + @test_case = nil + end end def before_examples(*args) diff --git a/spec/ci/reporter/cucumber_spec.rb b/spec/ci/reporter/cucumber_spec.rb index 5ebb459..51aea1f 100644 --- a/spec/ci/reporter/cucumber_spec.rb +++ b/spec/ci/reporter/cucumber_spec.rb @@ -84,11 +84,19 @@ def new_instance let(:test_suite) { double("test_suite", testcases: testcases) } let(:cucumber) { new_instance } let(:test_case) { double("test_case", start: nil, finish: nil, name: "Step Name") } + let(:scenario) { double("scenario") } let(:step) { double("step", :status => :passed, name: "Step Name") } before :each do allow(cucumber).to receive(:test_suite).and_return(test_suite) allow(CI::Reporter::TestCase).to receive(:new).and_return(test_case) + cucumber.before_feature_element(scenario) + + allow(cucumber).to receive(:feature_element_type).and_return(:scenario) + end + + after :each do + cucumber.after_feature_element(scenario) end context "before steps" do @@ -231,7 +239,21 @@ def new_instance allow(cucumber).to receive(:test_suite).and_return(test_suite) allow(CI::Reporter::TestCase).to receive(:new).and_return(test_case) + cucumber.before_feature_element(scenario_outline) cucumber.scenario_name(nil, "Scenario Name") + + allow(cucumber).to receive(:feature_element_type).and_return(:scenario_outline) + end + + after :each do + cucumber.after_feature_element(scenario_outline) + end + + context "before steps" do + it "does not create a new test case" do + expect(CI::Reporter::TestCase).to_not receive(:new) + cucumber.before_steps(step_collection) + end end context "processing a data table" do From 9ea4b6c7aa071d3462d1acd843aeef6243a4daae Mon Sep 17 00:00:00 2001 From: Charlie O'Keefe Date: Tue, 18 Oct 2016 15:51:41 -0600 Subject: [PATCH 9/9] Add scenario outline + tables to end-to-end tests --- acceptance/cucumber/cucumber_example.feature | 20 ++++++++++++ .../step_definitions/development_steps.rb | 16 ++++++++++ acceptance/verification_spec.rb | 31 ++++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/acceptance/cucumber/cucumber_example.feature b/acceptance/cucumber/cucumber_example.feature index 0e2cbd2..ea803ca 100644 --- a/acceptance/cucumber/cucumber_example.feature +++ b/acceptance/cucumber/cucumber_example.feature @@ -21,3 +21,23 @@ Feature: Example Cucumber feature Given that I can't code for peanuts And I write step definitions that throw exceptions Then I shouldn't be allowed out in public + + Scenario: Using a data table in a scenario + Given that I want to include the following data table + | Data Column 1 | Data Column 2 | Data Column 3 | + | Data Cell A | Data Cell B | Data Cell C | + | Data Cell D | Data Cell E | Data Cell F | + Then I should not see each row in the table treated as a separate example + + Scenario Outline: Using a scenario outline + Given that I might use a scenario outline with a data table and an examples table + And that I want to include the following data table + | Data Column 4 | Data Column 5 | Data Column 6 | + | Data Cell G | Data Cell H | Data Cell I | + | Data Cell J | Data Cell K | Data Cell L | + Then I want values for , , and + + Examples: + | Example Column 1 | Example Column 2 | Example Column 3 | + | Example Cell A | Example Cell B | Example Cell C | + | Example Cell D | Example Cell E | Example Cell F | diff --git a/acceptance/cucumber/step_definitions/development_steps.rb b/acceptance/cucumber/step_definitions/development_steps.rb index 4e3d157..2567c51 100644 --- a/acceptance/cucumber/step_definitions/development_steps.rb +++ b/acceptance/cucumber/step_definitions/development_steps.rb @@ -32,3 +32,19 @@ Then /^I shouldn't be allowed out in public$/ do end + +Given /^that I want to include the following data table$/ do |table| + expect(table).to be_a(Cucumber::Ast::Table) +end + +Then /^I should not see each row in the table treated as a separate example$/ do +end + +Given /^that I might use a scenario outline with a data table and an examples table$/ do +end + +Then /^I want values for (.*), (.*), and (.*)$/ do |arg_1, arg_2, arg_3| + expect(arg_1).to be_a(String) + expect(arg_2).to be_a(String) + expect(arg_3).to be_a(String) +end diff --git a/acceptance/verification_spec.rb b/acceptance/verification_spec.rb index f6e4dd7..4b09cc7 100644 --- a/acceptance/verification_spec.rb +++ b/acceptance/verification_spec.rb @@ -17,7 +17,7 @@ it { is_expected.to have(0).errors } it { expect(result.skipped_count).to be 1 } it { is_expected.to have(2).failures } - it { is_expected.to have(4).testcases } + it { is_expected.to have(7).testcases } it_behaves_like "a report with consistent attribute counts" it_behaves_like "assertions are not tracked" @@ -56,6 +56,35 @@ end end end + + describe "the test that uses a data table" do + subject(:testcase) { result.testcase('Using a data table in a scenario') } + + it { is_expected.to have(0).failures } + end + + context "the scenario outline with an examples table" do + + let(:scenario_name) { 'Using a scenario outline' } + let(:example_1_string) { '| Example Cell A | Example Cell B | Example Cell C |' } + let(:example_2_string) { '| Example Cell D | Example Cell E | Example Cell F |' } + + describe "the first example in the table" do + subject(:testcase) { + result.testcase("#{scenario_name} (outline: #{example_1_string})") + } + + it { is_expected.to have(0).failures } + end + + describe "the second example in the table" do + subject(:testcase) { + result.testcase("#{scenario_name} (outline: #{example_2_string})") + } + + it { is_expected.to have(0).failures } + end + end end def load_xml_result(path)