Created
September 11, 2013 03:29
-
-
Save enkessler/6519022 to your computer and use it in GitHub Desktop.
An example usage of the cucumber_analytics gem to add a unique identifier to every test case in a test suite. By making the identifier part of the test case itself it should be accessible both at run time via Cucumber's scenario object and from a static inspection of the source code by anything that can parse gherkin (e.g. cucumber_analytics).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require_relative '../../unique_test_case_tagger' | |
DEFAULT_FEATURE_FILE_NAME = 'test_feature' | |
DEFAULT_FILE_DIRECTORY = "#{File.dirname(__FILE__)}/../temp_files" | |
Before do | |
@default_feature_file_name = DEFAULT_FEATURE_FILE_NAME | |
@default_file_directory = DEFAULT_FILE_DIRECTORY | |
FileUtils.mkdir(@default_file_directory) | |
end | |
After do | |
FileUtils.remove_dir(@default_file_directory, true) | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Given /^the following feature file:$/ do |file_text| | |
@files_created ||= 0 | |
@test_directory = @default_file_directory | |
file_name = "#{@default_feature_file_name}_#{@files_created + 1}.feature" | |
File.open("#{@test_directory}/#{file_name}", 'w') { |file| | |
file.write(file_text) | |
} | |
@files_created += 1 | |
end | |
When(/^a tag prefix of "([^"]*)"$/) do |prefix| | |
@tag_prefix = prefix | |
end | |
When(/^the files? (?:is|are) processed$/) do | |
directory = CucumberAnalytics::Directory.new(@test_directory) | |
@feature_files = directory.feature_files.collect { |file| file.path } | |
UniqueTestCaseTagger.new.tag_tests(directory.path, @tag_prefix) | |
end | |
Then(/^the resulting file(?: "([^"]*)")? is:$/) do |file_index, expected_text| | |
file_index ||= 1 | |
actual_text = File.open(@feature_files[file_index - 1], 'r') { |file| actual_text = file.read } | |
actual_text.should == expected_text | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Feature: Tagging test cases for uniqueness | |
Scenario: Untagged scenario | |
Brand new test that hasn't been tagged yet. | |
Given the following feature file: | |
""" | |
Feature: | |
Scenario: | |
* a step | |
""" | |
And a tag prefix of "@test_case_" | |
When the file is processed | |
Then the resulting file is: | |
""" | |
Feature: | |
@test_case_1 | |
Scenario: | |
* a step | |
""" | |
Scenario: Tagged scenario | |
An older tests that doesn't need a new tag. | |
Given the following feature file: | |
""" | |
Feature: | |
@test_case_1 | |
Scenario: | |
* a step | |
""" | |
And a tag prefix of "@test_case_" | |
When the file is processed | |
Then the resulting file is: | |
""" | |
Feature: | |
@test_case_1 | |
Scenario: | |
* a step | |
""" | |
Scenario: Untagged outline | |
Brand new test that hasn't been tagged yet. | |
Given the following feature file: | |
""" | |
Feature: | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| param 1 | | |
| value 1 | | |
Examples: without rows | |
| param 1 | | |
""" | |
And a tag prefix of "@test_case_" | |
When the file is processed | |
Then the resulting file is: | |
""" | |
Feature: | |
@test_case_1 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 1-1 | value 1 | | |
Examples: without rows | |
| test_case_id | param 1 | | |
""" | |
Scenario: Tagged outline | |
An older tests that doesn't need a new tag. | |
Given the following feature file: | |
""" | |
Feature: | |
@test_case_1 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 1-1 | value 1 | | |
Examples: without rows | |
| test_case_id | param 1 | | |
""" | |
And a tag prefix of "@test_case_" | |
When the file is processed | |
Then the resulting file is: | |
""" | |
Feature: | |
@test_case_1 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 1-1 | value 1 | | |
Examples: without rows | |
| test_case_id | param 1 | | |
""" | |
Scenario: Partially tagged outline, has tag | |
An older test that has had new examples added to it. Possibly due to a | |
conversion from a scenario to an outline. | |
Given the following feature file: | |
""" | |
Feature: | |
@test_case_1 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| param 1 | | |
| value 1 | | |
Examples: without rows | |
| param 1 | | |
""" | |
And a tag prefix of "@test_case_" | |
When the file is processed | |
Then the resulting file is: | |
""" | |
Feature: | |
@test_case_1 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 1-1 | value 1 | | |
Examples: without rows | |
| test_case_id | param 1 | | |
""" | |
Scenario: Complex example | |
Given the following feature file: | |
""" | |
Feature: | |
Scenario: | |
* a step | |
@test_case_1 | |
Scenario: | |
* a step | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| param 1 | | |
| value 1 | | |
Examples: without rows | |
| param 1 | | |
Examples: some more rows | |
| param 1 | | |
| value 1 | | |
@test_case_2 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 2-1 | value 1 | | |
Examples: without rows | |
| test_case_id | param 1 | | |
Examples: some more rows | |
| test_case_id | param 1 | | |
| 2-2 | value 1 | | |
@test_case_3 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 3-1 | value 1 | | |
Examples: without rows | |
| param 1 | | |
Examples: some more rows | |
| param 1 | | |
| value 1 | | |
""" | |
And the following feature file: | |
""" | |
Feature: Just another feature to use up some tag indexes | |
New indexes start after the highest found index so as not to repeat the | |
indexes of older tests that might have since been removed. | |
@test_case_5 | |
Scenario: | |
* a step | |
""" | |
And a tag prefix of "@test_case_" | |
When the files are processed | |
Then the resulting file "1" is: | |
""" | |
Feature: | |
@test_case_6 | |
Scenario: | |
* a step | |
@test_case_1 | |
Scenario: | |
* a step | |
@test_case_7 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 7-1 | value 1 | | |
Examples: without rows | |
| test_case_id | param 1 | | |
Examples: some more rows | |
| test_case_id | param 1 | | |
| 7-2 | value 1 | | |
@test_case_2 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 2-1 | value 1 | | |
Examples: without rows | |
| test_case_id | param 1 | | |
Examples: some more rows | |
| test_case_id | param 1 | | |
| 2-2 | value 1 | | |
@test_case_3 | |
Scenario Outline: | |
* a step | |
Examples: with rows | |
| test_case_id | param 1 | | |
| 3-1 | value 1 | | |
Examples: without rows | |
| test_case_id | param 1 | | |
Examples: some more rows | |
| test_case_id | param 1 | | |
| 3-2 | value 1 | | |
""" | |
And the resulting file "2" is: | |
""" | |
Feature: Just another feature to use up some tag indexes | |
New indexes start after the highest found index so as not to repeat the | |
indexes of older tests that might have since been removed. | |
@test_case_5 | |
Scenario: | |
* a step | |
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'cucumber_analytics' | |
require 'unique_test_case_tagger' | |
# Project structure setup | |
your_location_here = "#{File.dirname(__FILE__)}" | |
feature_directory = "#{your_location_here}/features" | |
tag_prefix = '@test_case_' | |
# Analysis and output | |
UniqueTestCaseTagger.new.tag_tests(feature_directory, tag_prefix) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Transform /^(-?\d+)$/ do |number| | |
number.to_i | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'cucumber_analytics' | |
class UniqueTestCaseTagger | |
def initialize | |
@file_line_increases = Hash.new(0) | |
end | |
def tag_tests(feature_directory, tag_prefix) | |
warn("This script will potentially rewrite all of your feature files. Please be patient and remember to tip your source control system.") | |
# Object collection | |
world = CucumberAnalytics::World | |
directory = CucumberAnalytics::Directory.new(feature_directory) | |
tests = world.tests_in(directory) | |
# For this example, I am using a simple counter and prefix pattern to determine | |
# uniqueness. YMMV. | |
@tag_prefix = tag_prefix | |
@tag_pattern = Regexp.new("#{@tag_prefix}\\d+") | |
matching_tags = world.tags_in(directory).select { |tag| tag =~ @tag_pattern } | |
@last_index_used = matching_tags.collect { |tag| tag[/\d+/] }.sort.last.to_i | |
# Analysis and output | |
tests.each do |test| | |
case | |
when test.class.to_s =~ /Scenario/ | |
tag_scenario(test) | |
when test.class.to_s =~ /Outline/ | |
tag_outline(test) | |
else | |
raise("Unknown test type: #{test.class.to_s}") | |
end | |
end | |
end | |
private | |
def tag_scenario(test) | |
apply_tag_if_needed(test) | |
end | |
def tag_outline(test) | |
apply_tag_if_needed(test) | |
sub_id = 0 | |
test.examples.each do |example| | |
update_rows_if_needed(example, sub_id) | |
# For the purposes of this example, it is assumed that any new, untagged | |
# example groups are added at the end of existing example groups. This allows | |
# the additional complexity of determining which sub-ids are already being | |
# used to be avoided. | |
sub_id += example.rows.count | |
end | |
end | |
def apply_tag_if_needed(test) | |
unless has_id_tag?(test) | |
tag = "#{@tag_prefix}#{@last_index_used + 1}" | |
@last_index_used += 1 | |
tag_test(test, tag) | |
end | |
end | |
def has_id_tag?(test) | |
test.tags.any? { |tag| tag =~ @tag_pattern } | |
end | |
def tag_test(test, tag, padding_string = ' ') | |
feature_file = get_type_of_thing_for_thing(:feature_file, test) | |
file_path = feature_file.path | |
index_adjustment = @file_line_increases[file_path] | |
tag_index = (test.source_line - 1) + index_adjustment | |
file_lines = [] | |
File.open(file_path, 'r') { |file| file_lines = file.readlines } | |
file_lines.insert(tag_index, "#{padding_string}#{tag}\n") | |
File.open(file_path, 'w') { |file| file.print file_lines.join } | |
@file_line_increases[file_path] += 1 | |
end | |
def update_rows_if_needed(example, sub_id) | |
# For the purposes of this example, it is assumed that there are no incomplete | |
# example groups. Such a case could be handled but the additional complexity | |
# is outside of the scope of what is needed here to get the gist of it across. | |
unless has_id_parameter?(example) | |
test = get_type_of_thing_for_thing(:test, example) | |
feature_file = get_type_of_thing_for_thing(:feature_file, test) | |
file_path = feature_file.path | |
if has_id_tag?(test) | |
tag_index = tag_id(test)[/\d+/] | |
else | |
tag_index = @last_index_used | |
end | |
index_adjustment = @file_line_increases[file_path] | |
parameter_line_index = (example.row_elements.first.source_line - 1) + index_adjustment | |
file_lines = [] | |
File.open(file_path, 'r') { |file| file_lines = file.readlines } | |
update_parameter_row(file_lines, parameter_line_index) | |
example.row_elements[1..(example.row_elements.count - 1)].each do |row| | |
sub_id += 1 | |
row_id = "#{tag_index}-#{sub_id}".ljust(12) | |
index_adjustment = @file_line_increases[file_path] | |
row_line_index = (row.source_line - 1) + index_adjustment | |
update_value_row(file_lines, row_line_index, row_id) | |
end | |
File.open(file_path, 'w') { |file| file.print file_lines.join } | |
end | |
end | |
def tag_id(test) | |
test.tags.select { |tag| tag =~ @tag_pattern }.first | |
end | |
def has_id_parameter?(example) | |
example.parameters.any? { |parameter| parameter == 'test_case_id' } | |
end | |
# Some methods have more thought put into their names than others | |
def get_type_of_thing_for_thing(ancestor_type, thing) | |
ancestor_type = {:example => CucumberAnalytics::Example, | |
:feature_file => CucumberAnalytics::FeatureFile, | |
:test => CucumberAnalytics::TestElement | |
}[ancestor_type] | |
until thing.is_a?(ancestor_type) | |
thing = thing.parent_element | |
end | |
thing | |
end | |
def update_parameter_row(file_lines, line_index) | |
prepend_row!(file_lines, line_index, ' | test_case_id ') | |
end | |
def update_value_row(file_lines, line_index, row_id) | |
prepend_row!(file_lines, line_index, " | #{row_id} ") | |
end | |
def prepend_row!(file_lines, line_index, string) | |
old_row = file_lines[line_index] | |
new_row = string + old_row.lstrip | |
file_lines[line_index] = new_row | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment