cucumber_analytics 0.0.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +15 -0
  2. data/History.rdoc +16 -0
  3. data/README.rdoc +5 -3
  4. data/Rakefile +7 -2
  5. data/cucumber_analytics.gemspec +7 -5
  6. data/features/analysis/step_collection.feature +44 -45
  7. data/features/modeling/background_modeling.feature +14 -144
  8. data/features/modeling/directory_modeling.feature +3 -2
  9. data/features/modeling/doc_string_modeling.feature +46 -0
  10. data/features/modeling/example_modeling.feature +13 -34
  11. data/features/modeling/feature_file_modeling.feature +3 -2
  12. data/features/modeling/feature_modeling.feature +18 -80
  13. data/features/modeling/outline_modeling.feature +25 -164
  14. data/features/modeling/scenario_modeling.feature +17 -144
  15. data/features/modeling/step_modeling.feature +68 -0
  16. data/features/modeling/table_modeling.feature +41 -0
  17. data/features/step_definitions/background_steps.rb +12 -11
  18. data/features/step_definitions/directory_steps.rb +6 -3
  19. data/features/step_definitions/doc_string_steps.rb +50 -0
  20. data/features/step_definitions/{file_steps.rb → feature_file_steps.rb} +8 -2
  21. data/features/step_definitions/feature_steps.rb +8 -4
  22. data/features/step_definitions/outline_steps.rb +12 -6
  23. data/features/step_definitions/setup_steps.rb +2 -2
  24. data/features/step_definitions/spec_steps.rb +6 -3
  25. data/features/step_definitions/step_steps.rb +91 -0
  26. data/features/step_definitions/table_steps.rb +10 -0
  27. data/features/step_definitions/test_steps.rb +6 -10
  28. data/features/step_definitions/world_steps.rb +28 -19
  29. data/features/support/env.rb +0 -2
  30. data/lib/cucumber_analytics/background.rb +16 -0
  31. data/lib/cucumber_analytics/containing.rb +18 -0
  32. data/lib/cucumber_analytics/directory.rb +83 -0
  33. data/lib/cucumber_analytics/doc_string.rb +55 -0
  34. data/lib/cucumber_analytics/example.rb +100 -0
  35. data/lib/cucumber_analytics/feature.rb +120 -0
  36. data/lib/cucumber_analytics/feature_element.rb +22 -40
  37. data/lib/cucumber_analytics/feature_file.rb +74 -0
  38. data/lib/cucumber_analytics/outline.rb +49 -0
  39. data/lib/cucumber_analytics/parsing.rb +30 -0
  40. data/lib/cucumber_analytics/scenario.rb +31 -0
  41. data/lib/cucumber_analytics/step.rb +142 -32
  42. data/lib/cucumber_analytics/table.rb +51 -0
  43. data/lib/cucumber_analytics/taggable.rb +35 -0
  44. data/lib/cucumber_analytics/test_element.rb +36 -91
  45. data/lib/cucumber_analytics/version.rb +1 -1
  46. data/lib/cucumber_analytics/world.rb +109 -153
  47. data/lib/cucumber_analytics.rb +12 -8
  48. data/spec/integration/background_integration_spec.rb +18 -0
  49. data/spec/integration/directory_integration_spec.rb +24 -0
  50. data/spec/{feature_spec.rb → integration/feature_file_integration_spec.rb} +5 -5
  51. data/spec/integration/feature_integration_spec.rb +86 -0
  52. data/spec/integration/outline_integration_spec.rb +22 -0
  53. data/spec/integration/scenario_integration_spec.rb +18 -0
  54. data/spec/integration/step_integration_spec.rb +116 -0
  55. data/spec/integration/world_integration_spec.rb +40 -0
  56. data/spec/spec_helper.rb +7 -3
  57. data/spec/unit/background_unit_spec.rb +22 -0
  58. data/spec/unit/bare_bones_unit_specs.rb +13 -0
  59. data/spec/unit/containing_element_unit_specs.rb +17 -0
  60. data/spec/unit/directory_unit_spec.rb +91 -0
  61. data/spec/unit/doc_string_unit_spec.rb +65 -0
  62. data/spec/unit/example_unit_spec.rb +171 -0
  63. data/spec/unit/feature_element_unit_spec.rb +19 -0
  64. data/spec/unit/feature_element_unit_specs.rb +39 -0
  65. data/spec/unit/feature_file_unit_spec.rb +82 -0
  66. data/spec/unit/feature_unit_spec.rb +81 -0
  67. data/spec/unit/nested_element_unit_specs.rb +24 -0
  68. data/spec/unit/outline_unit_spec.rb +56 -0
  69. data/spec/unit/parsing_unit_spec.rb +21 -0
  70. data/spec/unit/prepopulated_unit_specs.rb +13 -0
  71. data/spec/unit/scenario_unit_spec.rb +36 -0
  72. data/spec/unit/step_unit_spec.rb +231 -0
  73. data/spec/unit/table_unit_spec.rb +52 -0
  74. data/spec/unit/taggable_unit_spec.rb +63 -0
  75. data/spec/unit/tagged_element_unit_specs.rb +48 -0
  76. data/spec/unit/test_element_unit_spec.rb +40 -0
  77. data/spec/unit/test_element_unit_specs.rb +31 -0
  78. data/spec/unit/world_unit_spec.rb +167 -0
  79. metadata +106 -41
  80. data/lib/cucumber_analytics/logging.rb +0 -28
  81. data/lib/cucumber_analytics/outline_example.rb +0 -110
  82. data/lib/cucumber_analytics/parsed_background.rb +0 -45
  83. data/lib/cucumber_analytics/parsed_directory.rb +0 -78
  84. data/lib/cucumber_analytics/parsed_feature.rb +0 -97
  85. data/lib/cucumber_analytics/parsed_file.rb +0 -199
  86. data/lib/cucumber_analytics/parsed_scenario.rb +0 -67
  87. data/lib/cucumber_analytics/parsed_scenario_outline.rb +0 -122
  88. data/spec/background_spec.rb +0 -23
  89. data/spec/directory_spec.rb +0 -18
  90. data/spec/example_spec.rb +0 -37
  91. data/spec/file_spec.rb +0 -20
  92. data/spec/outline_spec.rb +0 -32
  93. data/spec/scenario_spec.rb +0 -33
  94. data/spec/step_spec.rb +0 -24
@@ -0,0 +1,55 @@
1
+ module CucumberAnalytics
2
+
3
+ # A class modeling the Doc String of a Step.
4
+
5
+ class DocString
6
+
7
+ # The parent object that contains *self*
8
+ attr_accessor :parent_element
9
+
10
+ # The content type associated with the doc string
11
+ attr_accessor :content_type
12
+
13
+ # The contents of the doc string
14
+ attr_accessor :contents
15
+
16
+
17
+ # Creates a new DocString object and, if *source* is provided, populates
18
+ # the object.
19
+ def initialize(source = nil)
20
+ @contents = []
21
+
22
+ parsed_doc_string = process_source(source)
23
+
24
+ build_doc_string(parsed_doc_string) if parsed_doc_string
25
+ end
26
+
27
+
28
+ private
29
+
30
+
31
+ def process_source(source)
32
+ case
33
+ when source.is_a?(String)
34
+ parse_doc_string(source)
35
+ else
36
+ source
37
+ end
38
+ end
39
+
40
+ def parse_doc_string(source_text)
41
+ base_file_string = "Feature:\nScenario:\n* step\n"
42
+ source_text = base_file_string + source_text
43
+
44
+ parsed_file = Parsing::parse_text(source_text)
45
+
46
+ parsed_file.first['elements'].first['steps'].first['doc_string']
47
+ end
48
+
49
+ def build_doc_string(doc_string)
50
+ @content_type = doc_string['content_type'] == "" ? nil : doc_string['content_type']
51
+ @contents = doc_string['value'].split($/)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,100 @@
1
+ module CucumberAnalytics
2
+
3
+ # A class modeling a Cucumber Examples table.
4
+
5
+ class Example < FeatureElement
6
+
7
+ include Taggable
8
+
9
+ # The argument rows in the example table
10
+ attr_accessor :rows
11
+
12
+ # The parameters for the example table
13
+ attr_accessor :parameters
14
+
15
+
16
+ # Creates a new Example object and, if *source* is provided,
17
+ # populates the object.
18
+ def initialize(source = nil)
19
+ parsed_example = process_source(source)
20
+
21
+ super(parsed_example)
22
+
23
+ @tags = []
24
+ @rows = []
25
+ @parameters = []
26
+
27
+ build_example(parsed_example) if parsed_example
28
+ end
29
+
30
+ # Adds a row to the example table. The row can be given as a Hash of
31
+ # parameters and their corresponding values or as an Array of values which
32
+ # will be assigned in order.
33
+ def add_row(row)
34
+ case
35
+ when row.is_a?(Array)
36
+ @rows << Hash[@parameters.zip(row.collect { |value| value.strip })]
37
+ when row.is_a?(Hash)
38
+ @rows << row.each_value { |value| value.strip! }
39
+ else
40
+ raise(ArgumentError, "Can only add row from a Hash or an Array but received #{row.class}")
41
+ end
42
+ end
43
+
44
+ # Removes a row from the example table. The row can be given as a Hash of
45
+ # parameters and their corresponding values or as an Array of values
46
+ # which will be assigned in order.
47
+ def remove_row(row)
48
+ case
49
+ when row.is_a?(Array)
50
+ location = @rows.index { |row_hash| row_hash.values_at(*@parameters) == row.collect { |value| value.strip } }
51
+ when row.is_a?(Hash)
52
+ location = @rows.index { |row_hash| row_hash == row.each_value { |value| value.strip! } }
53
+ else
54
+ raise(ArgumentError, "Can only remove row from a Hash or an Array but received #{row.class}")
55
+ end
56
+
57
+ @rows.delete_at(location) if location
58
+ end
59
+
60
+
61
+ private
62
+
63
+
64
+ def process_source(source)
65
+ case
66
+ when source.is_a?(String)
67
+ parse_example(source)
68
+ else
69
+ source
70
+ end
71
+ end
72
+
73
+ def parse_example(source_text)
74
+ base_file_string = "Feature: Fake feature to parse\nScenario Outline:\n* fake step\n"
75
+ source_text = base_file_string + source_text
76
+
77
+ parsed_file = Parsing::parse_text(source_text)
78
+
79
+ parsed_file.first['elements'].first['examples'].first
80
+ end
81
+
82
+ def build_example(parsed_example)
83
+ populate_element_tags(parsed_example)
84
+ populate_example_parameters(parsed_example)
85
+ populate_example_rows(parsed_example)
86
+ end
87
+
88
+ def populate_example_parameters(parsed_example)
89
+ @parameters = parsed_example['rows'].first['cells']
90
+ end
91
+
92
+ def populate_example_rows(parsed_example)
93
+ parsed_example['rows'].shift
94
+ parsed_example['rows'].each do |row|
95
+ @rows << Hash[@parameters.zip(row['cells'])]
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,120 @@
1
+ module CucumberAnalytics
2
+
3
+ # A class modeling a Cucumber Feature.
4
+
5
+ class Feature < FeatureElement
6
+
7
+ include Taggable
8
+ include Containing
9
+
10
+
11
+ # The Background object contained by the Feature
12
+ attr_accessor :background
13
+
14
+ # The TestElement objects contained by the Feature
15
+ attr_accessor :tests
16
+
17
+
18
+ # Creates a new Feature object and, if *source* is provided, populates the
19
+ # object.
20
+ def initialize(source = nil)
21
+ parsed_feature = process_source(source)
22
+
23
+ super(parsed_feature)
24
+
25
+ @tags = []
26
+ @tests = []
27
+
28
+ build_feature(parsed_feature) if parsed_feature
29
+ end
30
+
31
+ # Returns true if the feature contains a background, false otherwise.
32
+ def has_background?
33
+ !@background.nil?
34
+ end
35
+
36
+ # Returns the scenarios contained in the feature.
37
+ def scenarios
38
+ @tests.select { |test| test.is_a? Scenario }
39
+ end
40
+
41
+ # Returns the outlines contained in the feature.
42
+ def outlines
43
+ @tests.select { |test| test.is_a? Outline }
44
+ end
45
+
46
+ # Returns the number of scenarios contained in the feature.
47
+ def scenario_count
48
+ scenarios.count
49
+ end
50
+
51
+ # Returns the number of outlines contained in the feature.
52
+ def outline_count
53
+ outlines.count
54
+ end
55
+
56
+ # Returns the number of tests contained in the feature.
57
+ def test_count
58
+ @tests.count
59
+ end
60
+
61
+ # Returns the number of test cases contained in the feature.
62
+ def test_case_count
63
+ scenario_count + outlines.reduce(0) { |outline_sum, outline|
64
+ outline_sum += outline.examples.reduce(0) { |example_sum, example|
65
+ example_sum += example.rows.count
66
+ }
67
+ }
68
+ end
69
+
70
+ # Returns the immediate child elements of the feature (i.e. its Background,
71
+ # Scenario, and Outline objects.
72
+ def contains
73
+ [@background] + @tests
74
+ end
75
+
76
+
77
+ private
78
+
79
+
80
+ def process_source(source)
81
+ case
82
+ when source.is_a?(String)
83
+ parse_feature(source)
84
+ else
85
+ source
86
+ end
87
+ end
88
+
89
+ def parse_feature(source_text)
90
+ parsed_file = Parsing::parse_text(source_text)
91
+
92
+ parsed_file.first
93
+ end
94
+
95
+ def build_feature(parsed_feature)
96
+ populate_element_tags(parsed_feature)
97
+ populate_feature_elements(parsed_feature)
98
+ end
99
+
100
+ def populate_feature_elements(parsed_feature)
101
+ elements = parsed_feature['elements']
102
+
103
+ if elements
104
+ elements.each do |element|
105
+ case element['keyword']
106
+ when 'Scenario'
107
+ @tests << build_child_element(Scenario, element)
108
+ when 'Scenario Outline'
109
+ @tests << build_child_element(Outline, element)
110
+ when 'Background'
111
+ @background = build_child_element(Background, element)
112
+ else
113
+ raise(ArgumentError, "Unknown keyword: #{element['keyword']}")
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ end
120
+ end
@@ -1,62 +1,44 @@
1
1
  module CucumberAnalytics
2
+
3
+ # A class modeling an basic element of a feature.
4
+
2
5
  class FeatureElement
3
6
 
7
+ # The name of the FeatureElement
8
+ attr_accessor :name
4
9
 
5
- attr_reader :name
6
- attr_reader :description
7
- attr_accessor :parent_element
10
+ # The description of the FeatureElement
11
+ attr_accessor :description
8
12
 
13
+ # The parent object that contains *self*
14
+ attr_accessor :parent_element
9
15
 
10
- # Creates a new FeatureElement object.
11
- def initialize(source_lines = nil)
12
- CucumberAnalytics::Logging.logger.info('FeatureElement#initialize')
13
16
 
17
+ # Creates a new FeatureElement object and, if *parsed_element* is provided,
18
+ # populates the object.
19
+ def initialize(parsed_element = nil)
14
20
  @name = ''
15
21
  @description =[]
22
+
23
+ build_feature_element(parsed_element) if parsed_element
16
24
  end
17
25
 
18
26
 
19
27
  private
20
28
 
21
29
 
22
- def parse_feature_element(source_lines)
23
- CucumberAnalytics::Logging.logger.info('FeatureElement#parse_feature_element')
24
-
25
- parse_feature_element_name(source_lines)
26
- parse_feature_element_description(source_lines)
30
+ def build_feature_element(parsed_element)
31
+ populate_feature_element_name(parsed_element)
32
+ populate_feature_element_description(parsed_element)
27
33
  end
28
34
 
29
- #todo - move this elsewhere
30
- def parse_feature_element_tags(source_lines)
31
- CucumberAnalytics::Logging.logger.info('FeatureElement#parse_feature_element_tags')
32
- CucumberAnalytics::Logging.logger.debug('source lines')
33
- source_lines.each do |line|
34
- CucumberAnalytics::Logging.logger.debug(line.chomp)
35
- end
36
-
37
- source_lines.take_while { |line| line !~ /^\s*(?:[A-Z a-z])+:/ }.tap do |tag_lines|
38
- tag_lines.delete_if { |line| World.ignored_line?(line)}
39
-
40
- tag_lines.join(' ').delete(' ').split('@').each do |tag|
41
- @tags << "@#{tag.strip}"
42
- end
43
- end
44
- @tags.shift
45
-
46
- while source_lines.first !~ /^\s*(?:[A-Z a-z])+:/
47
- source_lines.shift
48
- end
35
+ def populate_feature_element_name(parsed_element)
36
+ @name = parsed_element['name']
49
37
  end
50
38
 
51
- def parse_feature_element_name(source_lines)
52
- CucumberAnalytics::Logging.logger.info('FeatureElement#parse_feature_element_name')
53
- CucumberAnalytics::Logging.logger.debug('source lines')
54
- source_lines.each do |line|
55
- CucumberAnalytics::Logging.logger.debug(line.chomp)
56
- end
57
-
58
- @name.replace source_lines.first.match(/^\s*(?:[A-Z a-z])+:(.*)/)[1].strip
59
- source_lines.shift
39
+ def populate_feature_element_description(parsed_element)
40
+ @description = parsed_element['description'].split("\n").collect { |line| line.strip }
41
+ @description.delete('')
60
42
  end
61
43
 
62
44
  end
@@ -0,0 +1,74 @@
1
+ module CucumberAnalytics
2
+
3
+ # A class modeling a Cucumber .feature file.
4
+
5
+ class FeatureFile
6
+
7
+ include Containing
8
+
9
+
10
+ # The Feature objects contained by the FeatureFile
11
+ attr_accessor :features
12
+
13
+ # The parent object that contains *self*
14
+ attr_accessor :parent_element
15
+
16
+
17
+ # Creates a new FeatureFile object and, if *file_parsed* is provided,
18
+ # populates the object.
19
+ def initialize(file = nil)
20
+ @file = file
21
+ @features = []
22
+
23
+ if file
24
+ raise(ArgumentError, "Unknown file: #{file.inspect}") unless File.exists?(file)
25
+
26
+ parsed_file = parse_file(file)
27
+
28
+ build_file(parsed_file)
29
+ end
30
+ end
31
+
32
+ # Returns the name of the file.
33
+ def name
34
+ File.basename(@file.gsub('\\', '/'))
35
+ end
36
+
37
+ # Returns the path of the file.
38
+ def path
39
+ @file
40
+ end
41
+
42
+ # Returns the immediate child elements of the file(i.e. its Feature object).
43
+ def contains
44
+ @features
45
+ end
46
+
47
+ # Returns the number of features contained in the file.
48
+ def feature_count
49
+ @features.count
50
+ end
51
+
52
+ # Returns the Feature object contained by the FeatureFile.
53
+ def feature
54
+ @features.first
55
+ end
56
+
57
+
58
+ private
59
+
60
+
61
+ def parse_file(file_to_parse)
62
+ source_text = IO.read(file_to_parse)
63
+
64
+ Parsing::parse_text(source_text)
65
+ end
66
+
67
+ def build_file(parsed_file)
68
+ unless parsed_file.empty?
69
+ @features << build_child_element(Feature, parsed_file.first)
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,49 @@
1
+ module CucumberAnalytics
2
+
3
+ # A class modeling a Cucumber Scenario Outline.
4
+
5
+ class Outline < TestElement
6
+
7
+ include Taggable
8
+
9
+
10
+ # The Example objects contained by the Outline
11
+ attr_accessor :examples
12
+
13
+
14
+ # Creates a new Outline object and, if *source* is provided, populates the
15
+ # object.
16
+ def initialize(source = nil)
17
+ parsed_outline = process_source(source)
18
+
19
+ super(parsed_outline)
20
+
21
+ @tags = []
22
+ @examples = []
23
+
24
+ build_outline(parsed_outline) if parsed_outline
25
+ end
26
+
27
+ # Returns the immediate child elements of the outline (i.e. its Example
28
+ # objects.
29
+ def contains
30
+ @examples + @steps
31
+ end
32
+
33
+
34
+ private
35
+
36
+
37
+ def build_outline(parsed_outline)
38
+ populate_element_tags(parsed_outline)
39
+ populate_outline_examples(parsed_outline['examples']) if parsed_outline['examples']
40
+ end
41
+
42
+ def populate_outline_examples(parsed_examples)
43
+ parsed_examples.each do |example|
44
+ @examples << build_child_element(Example, example)
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ require 'stringio'
2
+ require 'gherkin/formatter/json_formatter'
3
+ require 'gherkin'
4
+
5
+ module CucumberAnalytics
6
+
7
+ # A module providing source text parsing functionality.
8
+
9
+ module Parsing
10
+
11
+ class << self
12
+
13
+ # Parses the Cucumber feature given in *source_text* and returns an array
14
+ # containing the hash representation of its logical structure.
15
+ def parse_text(source_text)
16
+ raise(ArgumentError, "Cannot parse #{source_text.class} objects. Strings only.") unless source_text.is_a?(String)
17
+
18
+ io = StringIO.new
19
+ formatter = Gherkin::Formatter::JSONFormatter.new(io)
20
+ parser = Gherkin::Parser::Parser.new(formatter)
21
+ parser.parse(source_text, 'fake_file.txt', 0)
22
+ formatter.done
23
+
24
+ JSON.parse(io.string)
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module CucumberAnalytics
2
+
3
+ # A class modeling a Cucumber Scenario.
4
+
5
+ class Scenario < TestElement
6
+
7
+ include Taggable
8
+
9
+
10
+ # Creates a new Scenario object and, if *source* is provided, populates the
11
+ # object.
12
+ def initialize(source = nil)
13
+ parsed_scenario = process_source(source)
14
+
15
+ super(parsed_scenario)
16
+
17
+ @tags = []
18
+
19
+ build_scenario(parsed_scenario) if parsed_scenario
20
+ end
21
+
22
+
23
+ private
24
+
25
+
26
+ def build_scenario(scenario)
27
+ populate_element_tags(scenario)
28
+ end
29
+
30
+ end
31
+ end