Skip to content

Instantly share code, notes, and snippets.

@enkessler
Created September 11, 2013 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save enkessler/6519022 to your computer and use it in GitHub Desktop.
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).
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
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
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
"""
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)
Transform /^(-?\d+)$/ do |number|
number.to_i
end
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