cucumber_characteristics 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,5 +15,3 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
-
19
- features/characteristics/
data/README.md CHANGED
@@ -33,6 +33,7 @@ You can configure the export of step characteristics via the following (defaults
33
33
  CucumberCharacteristics.configure do |config|
34
34
  config.export_json = true
35
35
  config.export_html = true
36
+ config.precision = 4
36
37
  config.target_filename = 'cucumber_step_characteristics'
37
38
  config.relative_path = 'features/characteristics'
38
39
  end
@@ -48,6 +49,8 @@ depending on the options specified.
48
49
 
49
50
  The JSON option is provided for convenience in case there is a further use case/analysis required that is not provided by the gem.
50
51
 
52
+ An example can be found [here](features/characteristics/cucumber_step_characteristics.html)
53
+
51
54
  ## Problem
52
55
 
53
56
  The formatting hooks on the face of it provide the necessary event points to profile any given feature file.
@@ -0,0 +1,4 @@
1
+ Feature: As a user I want to understand where my tests are spending their time in a scenaio with an ambiguous step
2
+
3
+ Scenario: Timings for normal scenario
4
+ Given I wait some seconds
@@ -0,0 +1,358 @@
1
+ <!DOCTYPE html>
2
+ <html lang='en'>
3
+ <head>
4
+ <meta charset='utf-8'>
5
+ <meta content='IE=edge' http-equiv='X-UA-Compatible'>
6
+ <meta content='width=device-width, initial-scale=1' name='viewport'>
7
+ <meta content='Cucumber Step Characteristics' name='description'>
8
+ <title>Cucumber Step Characteristics</title>
9
+ <link href='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css' rel='stylesheet'>
10
+ <link href='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css' rel='stylesheet'>
11
+ <!-- Custom styles for this layout -->
12
+ <style>
13
+ body {
14
+ min-height: 2000px;
15
+ padding-top: 70px;
16
+ }
17
+
18
+ td {
19
+ white-space: nowrap;
20
+ }
21
+
22
+ table.tablesorter thead tr .headerSortUp {
23
+ background-color: #8dbdd8;
24
+ }
25
+ table.tablesorter thead tr .headerSortDown {
26
+ background-color: #8dbdd8;
27
+ }
28
+ </style>
29
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
30
+ <!--[if lt IE 9]>
31
+ <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
32
+ <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
33
+ <![endif]-->
34
+ </head>
35
+ <body>
36
+ <div class='container'>
37
+ <div class='page-header'>
38
+ <h1>Cucumber step characteristics</h1>
39
+ </div>
40
+ <div class='alert alert-info'>
41
+ <span>
42
+ 10 Scenarios,
43
+ 36 Steps completed
44
+ <span class='text-muted'>
45
+ (Failed: 2, Passed: 31, Skipped: 2, Undefined: 1)<br>
46
+ </span>
47
+ 1/2 failures due to ambiguous matches<br>
48
+ Test duration 12.0048s.
49
+ <span class='text-muted'>
50
+ (11.9254s steps, 0.0794s non-steps)
51
+ </span>
52
+ </span>
53
+ <span class='text-muted pull-right small'>
54
+ Finished on 2014-06-25 20:13:34 -0400
55
+ </span>
56
+ </div>
57
+ <ul class='nav nav-tabs'>
58
+ <li class='active'>
59
+ <a data-toggle='tab' href='#steps'>Steps</a>
60
+ </li>
61
+ <li>
62
+ <a data-toggle='tab' href='#features'>Features</a>
63
+ </li>
64
+ </ul>
65
+ <div class='tab-content'>
66
+ <div class='tab-pane active' id='steps'>
67
+ <table class='table table-striped table-bordered table-condensed tablesorter' id='profile_table'>
68
+ <thead>
69
+ <tr>
70
+ <th>Step</th>
71
+ <th>Total time</th>
72
+ <th>Passed</th>
73
+ <th>Average</th>
74
+ <th>Fastest</th>
75
+ <th>Slowest</th>
76
+ <th>Variation</th>
77
+ <th>Variance</th>
78
+ <th>Std Deviation</th>
79
+ <th>Skipped</th>
80
+ <th>Error</th>
81
+ <th>Undef</th>
82
+ <th>Total count</th>
83
+ </tr>
84
+ </thead>
85
+ <tbody>
86
+ <tr>
87
+ <td>
88
+ <abbr title='/^I wait ([\d\.]+) seconds$/'>features/step_definitions/wait_steps.rb:1</abbr>
89
+ </td>
90
+ <td>11.9254</td>
91
+ <td>
92
+ <abbr title='features/failure.feature:4&#x000A;features/pending.feature:4&#x000A;features/scenario.feature:4&#x000A;features/scenario.feature:5&#x000A;features/scenario.feature:6&#x000A;features/scenario_outline.feature:4 (x2)&#x000A;features/scenario_outline.feature:5 (x2)&#x000A;features/scenario_outline.feature:6 (x2)&#x000A;features/scenario_outline.feature:7 (x2)&#x000A;features/scenario_outline_with_background.feature:4 (x2)&#x000A;features/scenario_outline_with_background.feature:7 (x2)&#x000A;features/scenario_outline_with_background.feature:8 (x2)&#x000A;features/scenario_outline_with_background.feature:9 (x2)&#x000A;features/scenario_outline_with_background.feature:10 (x2)&#x000A;features/scenario_with_background.feature:4 (x2)&#x000A;features/scenario_with_background.feature:7&#x000A;features/scenario_with_background.feature:8&#x000A;features/scenario_with_background.feature:9&#x000A;features/scenario_with_background.feature:12&#x000A;features/scenario_with_background.feature:13&#x000A;features/scenario_with_background.feature:14'>31</abbr>
93
+ </td>
94
+ <td>0.3847</td>
95
+ <td>0.1006</td>
96
+ <td>1.0004</td>
97
+ <td>0.8998</td>
98
+ <td>0.0562</td>
99
+ <td>0.2371</td>
100
+ <td>
101
+ <abbr title='features/failure.feature:6&#x000A;features/pending.feature:6'>2</abbr>
102
+ </td>
103
+ <td>
104
+ 0
105
+ </td>
106
+ <td>
107
+ 0
108
+ </td>
109
+ <td>33</td>
110
+ </tr>
111
+ <tr>
112
+ <td>
113
+ <abbr title='/^I fail$/'>features/step_definitions/fail_steps.rb:1</abbr>
114
+ </td>
115
+ <td>-</td>
116
+ <td>
117
+ 0
118
+ </td>
119
+ <td>-</td>
120
+ <td>-</td>
121
+ <td>-</td>
122
+ <td>-</td>
123
+ <td>-</td>
124
+ <td>-</td>
125
+ <td>
126
+ 0
127
+ </td>
128
+ <td>
129
+ <abbr title='features/failure.feature:5'>1</abbr>
130
+ </td>
131
+ <td>
132
+ 0
133
+ </td>
134
+ <td>1</td>
135
+ </tr>
136
+ <tr>
137
+ <td>
138
+ <abbr title=''>I call a pending step</abbr>
139
+ </td>
140
+ <td>-</td>
141
+ <td>
142
+ 0
143
+ </td>
144
+ <td>-</td>
145
+ <td>-</td>
146
+ <td>-</td>
147
+ <td>-</td>
148
+ <td>-</td>
149
+ <td>-</td>
150
+ <td>
151
+ 0
152
+ </td>
153
+ <td>
154
+ 0
155
+ </td>
156
+ <td>
157
+ <abbr title='features/pending.feature:5'>1</abbr>
158
+ </td>
159
+ <td>1</td>
160
+ </tr>
161
+ </tbody>
162
+ </table>
163
+ </div>
164
+ <div class='tab-pane' id='features'>
165
+ <table class='table table-striped table-bordered table-condensed' id='feature_table'>
166
+ <thead>
167
+ <tr>
168
+ <th>Feature</th>
169
+ <th>Total time</th>
170
+ <th>Step count</th>
171
+ <th>Status</th>
172
+ <th></th>
173
+ </tr>
174
+ </thead>
175
+ <tbody>
176
+ <tr class='danger'>
177
+ <td>features/failure.feature:3</td>
178
+ <td>1.0004</td>
179
+ <td>3</td>
180
+ <td>failed</td>
181
+ <td>
182
+ </td>
183
+ </tr>
184
+ <tr class='danger'>
185
+ <td>features/ambiguous.feature:3</td>
186
+ <td>-</td>
187
+ <td>1</td>
188
+ <td>failed</td>
189
+ <td>
190
+ </td>
191
+ </tr>
192
+ <tr class='warning'>
193
+ <td>features/pending.feature:3</td>
194
+ <td>1.0004</td>
195
+ <td>3</td>
196
+ <td>undefined</td>
197
+ <td>
198
+ </td>
199
+ </tr>
200
+ <tr class=''>
201
+ <td>features/scenario_outline_with_background.feature:6</td>
202
+ <td>3.6076</td>
203
+ <td>10</td>
204
+ <td>passed</td>
205
+ <td>
206
+ <button class='btn btn-primary' data-target='#ee4ab50980effe45ea18c9afc38e97e5' data-toggle='modal'>
207
+ 2 Examples
208
+ </button>
209
+ </td>
210
+ </tr>
211
+ <tr class=''>
212
+ <td>features/scenario_with_background.feature:11</td>
213
+ <td>2.2036</td>
214
+ <td>4</td>
215
+ <td>passed</td>
216
+ <td>
217
+ </td>
218
+ </tr>
219
+ <tr class=''>
220
+ <td>features/scenario_outline.feature:3</td>
221
+ <td>1.6051</td>
222
+ <td>8</td>
223
+ <td>passed</td>
224
+ <td>
225
+ <button class='btn btn-primary' data-target='#76f61671f111ebbc40b80f6aa9f5f838' data-toggle='modal'>
226
+ 2 Examples
227
+ </button>
228
+ </td>
229
+ </tr>
230
+ <tr class=''>
231
+ <td>features/scenario.feature:3</td>
232
+ <td>1.5057</td>
233
+ <td>3</td>
234
+ <td>passed</td>
235
+ <td>
236
+ </td>
237
+ </tr>
238
+ <tr class=''>
239
+ <td>features/scenario_with_background.feature:6</td>
240
+ <td>1.0026</td>
241
+ <td>4</td>
242
+ <td>passed</td>
243
+ <td>
244
+ </td>
245
+ </tr>
246
+ </tbody>
247
+ </table>
248
+ <div aria-hidden='true' aria-labelledby='myLargeModalLabel' class='modal fade bs-example-modal-lg' id='ee4ab50980effe45ea18c9afc38e97e5' role='dialog' tabindex='-1'>
249
+ <div class='modal-dialog modal-lg'>
250
+ <div class='modal-content'>
251
+ <div class='modal-header'>
252
+ <h4>features/scenario_outline_with_background.feature:6</h4>
253
+ </div>
254
+ <div class='modal-body'>
255
+ <table class='table table-striped table-bordered table-condensed'>
256
+ <thead>
257
+ <tr>
258
+ <th>Example</th>
259
+ <th>Total time</th>
260
+ <th>Step count</th>
261
+ <th>Status</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody>
265
+ <tr class=''>
266
+ <td>| 0.5 | 0.6 | 0.7 |</td>
267
+ <td>2.4043</td>
268
+ <td>5</td>
269
+ <td>passed</td>
270
+ </tr>
271
+ <tr class=''>
272
+ <td>| 0.1 | 0.2 | 0.3 |</td>
273
+ <td>1.2033</td>
274
+ <td>5</td>
275
+ <td>passed</td>
276
+ </tr>
277
+ </tbody>
278
+ <tfooter>
279
+ <tr>
280
+ <th></th>
281
+ <th>3.6076</th>
282
+ <th>10</th>
283
+ <th>passed</th>
284
+ </tr>
285
+ </tfooter>
286
+ </table>
287
+ </div>
288
+ <div class='modal-footer'>
289
+ <button class='btn btn-default' data-dismiss='modal' type='button'>
290
+ Close
291
+ </button>
292
+ </div>
293
+ </div>
294
+ </div>
295
+ </div>
296
+ <div aria-hidden='true' aria-labelledby='myLargeModalLabel' class='modal fade bs-example-modal-lg' id='76f61671f111ebbc40b80f6aa9f5f838' role='dialog' tabindex='-1'>
297
+ <div class='modal-dialog modal-lg'>
298
+ <div class='modal-content'>
299
+ <div class='modal-header'>
300
+ <h4>features/scenario_outline.feature:3</h4>
301
+ </div>
302
+ <div class='modal-body'>
303
+ <table class='table table-striped table-bordered table-condensed'>
304
+ <thead>
305
+ <tr>
306
+ <th>Example</th>
307
+ <th>Total time</th>
308
+ <th>Step count</th>
309
+ <th>Status</th>
310
+ </tr>
311
+ </thead>
312
+ <tbody>
313
+ <tr class=''>
314
+ <td>| 0.3 | 0.2 | 0.1 |</td>
315
+ <td>0.8025</td>
316
+ <td>4</td>
317
+ <td>passed</td>
318
+ </tr>
319
+ <tr class=''>
320
+ <td>| 0.1 | 0.2 | 0.3 |</td>
321
+ <td>0.8025</td>
322
+ <td>4</td>
323
+ <td>passed</td>
324
+ </tr>
325
+ </tbody>
326
+ <tfooter>
327
+ <tr>
328
+ <th></th>
329
+ <th>1.6051</th>
330
+ <th>8</th>
331
+ <th>passed</th>
332
+ </tr>
333
+ </tfooter>
334
+ </table>
335
+ </div>
336
+ <div class='modal-footer'>
337
+ <button class='btn btn-default' data-dismiss='modal' type='button'>
338
+ Close
339
+ </button>
340
+ </div>
341
+ </div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ </div>
347
+ <script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'></script>
348
+ <script src='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js'></script>
349
+ <script src='https://raw.githubusercontent.com/christianbach/tablesorter/master/jquery.tablesorter.min.js'></script>
350
+ <script>
351
+ $(document).ready(function()
352
+ {
353
+ $("#profile_table").tablesorter( {sortList: [[1,1],[3,1]], widgets: ['zebra']} );
354
+ }
355
+ );
356
+ </script>
357
+ </body>
358
+ </html>
@@ -0,0 +1 @@
1
+ [["features/step_definitions/wait_steps.rb:1",{"total_count":33,"passed":{"count":31,"feature_location":{"features/failure.feature:4":[1.000400775],"features/pending.feature:4":[1.000400775],"features/scenario.feature:4":[0.501914414],"features/scenario.feature:5":[0.501914414],"features/scenario.feature:6":[0.501914414],"features/scenario_outline.feature:4":[0.100597662,0.30053452],"features/scenario_outline.feature:5":[0.200701096,0.200701096],"features/scenario_outline.feature:6":[0.30053452,0.100597662],"features/scenario_outline.feature:7":[0.200701096,0.200701096],"features/scenario_outline_with_background.feature:4":[0.400732052,0.400732052],"features/scenario_outline_with_background.feature:7":[0.100597662,0.501914414],"features/scenario_outline_with_background.feature:8":[0.200701096,0.600408111],"features/scenario_outline_with_background.feature:9":[0.30053452,0.700576606],"features/scenario_outline_with_background.feature:10":[0.200701096,0.200701096],"features/scenario_with_background.feature:4":[0.400732052,0.400732052],"features/scenario_with_background.feature:7":[0.100597662],"features/scenario_with_background.feature:8":[0.200701096],"features/scenario_with_background.feature:9":[0.30053452],"features/scenario_with_background.feature:12":[0.501914414],"features/scenario_with_background.feature:13":[0.600408111],"features/scenario_with_background.feature:14":[0.700576606]}},"failed":{"count":0,"feature_location":{}},"skipped":{"count":2,"feature_location":{"features/failure.feature:6":[],"features/pending.feature:6":[]}},"undefined":{"count":0,"feature_location":{}},"regexp":"/^I wait ([\\d\\.]+) seconds$/","fastest":0.100597662,"slowest":1.000400775,"average":0.3846906050967743,"total_duration":11.925408758000003,"standard_deviation":0.2370893233738576,"variation":0.8998031129999999,"variance":0.05621134725787363}],["features/step_definitions/fail_steps.rb:1",{"total_count":1,"passed":{"count":0,"feature_location":{}},"failed":{"count":1,"feature_location":{"features/failure.feature:5":[]}},"skipped":{"count":0,"feature_location":{}},"undefined":{"count":0,"feature_location":{}},"regexp":"/^I fail$/","fastest":null,"slowest":null,"average":null,"total_duration":null,"standard_deviation":null,"variation":null}],["I call a pending step",{"total_count":1,"passed":{"count":0,"feature_location":{}},"failed":{"count":0,"feature_location":{}},"skipped":{"count":0,"feature_location":{}},"undefined":{"count":1,"feature_location":{"features/pending.feature:5":[]}},"fastest":null,"slowest":null,"average":null,"total_duration":null,"standard_deviation":null,"variation":null}]]
@@ -1,6 +1,6 @@
1
1
  Feature: As a user I want to understand where my tests are spending their time in a scenario
2
2
 
3
3
  Scenario: Timings for normal scenario
4
- Given I wait 1 seconds
5
- When I wait 1 seconds
6
- Then I wait 1 seconds
4
+ Given I wait 0.5 seconds
5
+ When I wait 0.5 seconds
6
+ Then I wait 0.5 seconds
@@ -8,4 +8,4 @@ Feature: As a user I want to understand where my tests are spending their time i
8
8
  Examples:
9
9
  | given_wait | when_wait | then_wait |
10
10
  | 0.1 | 0.2 | 0.3 |
11
- | 0.5 | 0.6 | 0.7 |
11
+ | 0.3 | 0.2 | 0.1 |
@@ -0,0 +1,5 @@
1
+ Given(/^I wait some/) do
2
+ end
3
+
4
+ Given(/^I wait some seconds$/) do
5
+ end
@@ -2,11 +2,12 @@ module CucumberCharacteristics
2
2
 
3
3
  class Configuration
4
4
 
5
- attr_accessor :export_json, :export_html, :target_filename, :relative_path
5
+ attr_accessor :export_json, :export_html, :target_filename, :relative_path, :precision
6
6
 
7
7
  def initialize
8
8
  @export_json = true
9
9
  @export_html = true
10
+ @precision = 4
10
11
  @target_filename = 'cucumber_step_characteristics'
11
12
  @relative_path = 'features/characteristics'
12
13
  end
@@ -2,14 +2,16 @@
2
2
 
3
3
  module Cucumber
4
4
  class StepMatch
5
- old_invoke = instance_method(:invoke)
6
- attr_reader :duration
5
+ if self.method_defined?(:invoke)
6
+ old_invoke = instance_method(:invoke)
7
+ attr_reader :duration
7
8
 
8
- define_method(:invoke) do | multiline_arg |
9
- start_time = Time.now
10
- ret = old_invoke.bind(self).(multiline_arg)
11
- @duration = Time.now - start_time
12
- ret
9
+ define_method(:invoke) do | multiline_arg |
10
+ start_time = Time.now
11
+ ret = old_invoke.bind(self).(multiline_arg)
12
+ @duration = Time.now - start_time
13
+ ret
14
+ end
13
15
  end
14
16
  end
15
17
  end
@@ -20,5 +22,11 @@ module Cucumber
20
22
  class StepInvocation
21
23
  attr_reader :step_match
22
24
  end
25
+
26
+ class OutlineTable
27
+ class ExampleRow
28
+ attr_reader :step_invocations
29
+ end
30
+ end
23
31
  end
24
32
  end
@@ -1,4 +1,5 @@
1
1
  require 'haml'
2
+ require 'digest/md5'
2
3
 
3
4
  module CucumberCharacteristics
4
5
 
@@ -35,7 +36,7 @@ module CucumberCharacteristics
35
36
  # HELPERS
36
37
 
37
38
  def format_ts(t)
38
- t ? sprintf("%0.4f", t) : '-'
39
+ t ? sprintf("%0.#{@config.precision}f", t) : '-'
39
40
  end
40
41
 
41
42
  def format_step_usage(step_feature_data)
@@ -6,8 +6,27 @@ module CucumberCharacteristics
6
6
  @runtime = runtime
7
7
  @io = io
8
8
  @options = options
9
+ @features = {}
9
10
  end
10
11
 
12
+ # def before_feature(scenario)
13
+ # pp scenario.class.name
14
+ # if scenario.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)
15
+ # feature_location = scenario.scenario_outline.file_colon_line
16
+ # feature_name = scenario.scenario_outline.name.chomp
17
+ # feature_example = scenario.name
18
+ # @features[feature_location] ||= {}
19
+ # @features[feature_location][feature_example] ||= {start_time: Time.now}
20
+ # else
21
+ # feature_location = scenario.file_colon_line
22
+ # feature_name = scenario.name.chomp
23
+ # @features[feature_location] ||= {start_time: Time.now}
24
+ # end
25
+ # end
26
+
27
+ # def after_feature(scenario)
28
+ # end
29
+
11
30
  def after_features(features)
12
31
  profile = ProfileData.new(@runtime, features)
13
32
  Exporter.new(profile).export
@@ -7,36 +7,103 @@ module CucumberCharacteristics
7
7
  def_delegators :@runtime, :scenarios, :steps
8
8
  attr_reader :duration
9
9
 
10
- STATUS = [:passed, :failed, :skipped, :undefined ]
10
+ STATUS_ORDER = {passed: 0, failed: 2000, skipped: 1000, undefined: 500}
11
+
12
+ STATUS = STATUS_ORDER.keys
11
13
 
12
14
  def initialize(runtime, features)
13
15
  @runtime = runtime
14
16
  @duration = features.duration
17
+ @features = features
18
+ end
19
+
20
+ def ambiguous_count
21
+ @runtime.steps.count{|s| ambiguous?(s)}
22
+ end
23
+
24
+ def unmatched_steps
25
+ unmatched = {}
26
+ @runtime.unmatched_step_definitions.each do |u|
27
+ unmatched[u.file_colon_line] = u.regexp_source
28
+ end
29
+ unmatched.sort
30
+ end
31
+
32
+ def has_unmatched_steps?
33
+ unmatched_steps.count > 0
34
+ end
35
+
36
+ def feature_profiles
37
+ feature_profiles = { }
38
+ @runtime.scenarios.each do |f|
39
+ if f.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)
40
+ feature_id = f.scenario_outline.file_colon_line
41
+ feature_profiles[feature_id] ||= {name: f.scenario_outline.name, total_duration: 0, step_count: 0, example_count: 0, examples: {} }
42
+ example_id = f.name
43
+ feature_profiles[feature_id][:examples][example_id] ||= {total_duration: 0, step_count: 0}
44
+ feature_profiles[feature_id][:examples][example_id][:total_duration] = f.step_invocations.select{|s| s.status == :passed}.map{|s| s.step_match.duration}.inject(&:+)
45
+ feature_profiles[feature_id][:examples][example_id][:step_count] = f.step_invocations.count
46
+ feature_profiles[feature_id][:examples][example_id][:status] = f.status
47
+ else
48
+ feature_id = f.file_colon_line
49
+ feature_profiles[feature_id] = {name: f.name, total_duration: 0, step_count: 0}
50
+ feature_profiles[feature_id][:total_duration] = f.steps.select{|s| s.status == :passed}.map{|s| s.step_match.duration}.inject(&:+)
51
+ feature_profiles[feature_id][:step_count] = f.steps.count
52
+ feature_profiles[feature_id][:status] = f.status
53
+ end
54
+ end
55
+ with_feature_calculations(feature_profiles)
56
+ end
57
+
58
+ def with_feature_calculations(feature_profiles)
59
+ feature_profiles.each do |feature, meta|
60
+ if meta[:examples]
61
+ feature_profiles[feature][:example_count] = meta[:examples].keys.count
62
+ feature_profiles[feature][:total_duration] = meta[:examples].map{|e,m| m[:total_duration]}.inject(&:+)
63
+ feature_profiles[feature][:step_count] = meta[:examples].map{|e,m| m[:step_count]}.inject(&:+)
64
+ feature_profiles[feature][:examples] = feature_profiles[feature][:examples].sort_by{|k, v| v[:total_duration]}.reverse
65
+ feature_profiles[feature][:status] = if meta[:examples].all?{|e,m| m[:status] == :passed}
66
+ :passed
67
+ elsif meta[:examples].any?{|e,m| m[:status] == :failed}
68
+ :failed
69
+ elsif meta[:examples].any?{|e,m| m[:status] == :skipped}
70
+ :skipped
71
+ else
72
+ :unknown
73
+ end
74
+ end
75
+ end
76
+ feature_profiles.sort_by{|k, v| (STATUS_ORDER[v[:status]]||0)+(v[:total_duration] || 0)}.reverse
15
77
  end
16
78
 
17
79
  def step_profiles
18
- #return @step_profiles if @step_profiles
19
80
  step_profiles = {}
20
81
  @runtime.steps.each do |s|
21
- step_name = s.status == :undefined ? s.name : s.step_match.step_definition.file_colon_line
22
- # Initialize data structure
23
- step_profiles[step_name] ||= { :total_count => 0}
24
- STATUS.each {|status| step_profiles[step_name][status] ||= {:count => 0, :feature_location => {} }}
25
- feature_location = s.file_colon_line
26
- step_profiles[step_name][s.status][:count] += 1
27
- step_profiles[step_name][:total_count] += 1
28
- step_profiles[step_name][s.status][:feature_location][feature_location] ||= []
29
- if s.status != :undefined
30
- step_profiles[step_name][:regexp] = s.step_match.step_definition.regexp_source
31
- if s.status == :passed
32
- step_profiles[step_name][s.status][:feature_location][feature_location] << s.step_match.duration
82
+ unless ambiguous?(s)
83
+ step_name = s.status == :undefined ? s.name : s.step_match.step_definition.file_colon_line
84
+ # Initialize data structure
85
+ step_profiles[step_name] ||= { :total_count => 0}
86
+ STATUS.each {|status| step_profiles[step_name][status] ||= {:count => 0, :feature_location => {} }}
87
+ feature_location = s.file_colon_line
88
+ step_profiles[step_name][s.status][:count] += 1
89
+ step_profiles[step_name][:total_count] += 1
90
+ step_profiles[step_name][s.status][:feature_location][feature_location] ||= []
91
+ if s.status != :undefined
92
+ step_profiles[step_name][:regexp] = s.step_match.step_definition.regexp_source
93
+ if s.status == :passed
94
+ step_profiles[step_name][s.status][:feature_location][feature_location] << s.step_match.duration
95
+ end
33
96
  end
34
97
  end
35
98
  end
36
- with_calculations(step_profiles)
99
+ with_step_calculations(step_profiles)
37
100
  end
38
101
 
39
- def with_calculations(step_profiles)
102
+ def ambiguous?(step)
103
+ step.status == :failed && step.step_match.step_definition.nil?
104
+ end
105
+
106
+ def with_step_calculations(step_profiles)
40
107
  step_profiles.each do |step, meta|
41
108
  meta.merge!(fastest: nil, slowest: nil, average: nil, total_duration: nil, standard_deviation: nil, variation: nil)
42
109
  next unless meta[:passed][:count] > 0
@@ -55,7 +122,7 @@ module CucumberCharacteristics
55
122
  step_profiles[step][:variance] = sum/(timings.length ).to_f
56
123
  step_profiles[step][:standard_deviation] = Math.sqrt( step_profiles[step][:variance])
57
124
  end
58
- step_profiles
125
+ step_profiles.sort_by{|k, v| v[:total_duration]||0}.reverse
59
126
  end
60
127
 
61
128
  def step_duration
@@ -83,6 +150,10 @@ module CucumberCharacteristics
83
150
  status
84
151
  end
85
152
 
153
+ def step_count(status)
154
+ step_count_by_status[status]
155
+ end
156
+
86
157
  def scenario_count_by_status
87
158
  status = {}
88
159
  @runtime.scenarios.each do |s|
@@ -94,5 +165,4 @@ module CucumberCharacteristics
94
165
 
95
166
  end
96
167
 
97
-
98
168
  end
@@ -1,3 +1,3 @@
1
1
  module CucumberCharacteristics
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -38,63 +38,140 @@
38
38
  #{profile.scenarios.count} Scenarios,
39
39
  #{profile.steps.count} Steps completed
40
40
  %span.text-muted
41
- (#{step_status_summary(profile)})
42
- <br>
41
+ (#{step_status_summary(profile)})<br>
42
+ - if profile.ambiguous_count > 0
43
+ #{profile.ambiguous_count}/#{profile.step_count(:failed)} failures due to ambiguous matches<br>
43
44
  Test duration #{format_ts(profile.duration)}s.
44
45
  %span.text-muted
45
46
  (#{format_ts(profile.step_duration)}s steps, #{format_ts(profile.nonstep_duration)}s non-steps)
46
47
  %span.text-muted.pull-right.small
47
48
  Finished on #{Time.now}
48
49
 
49
- %table.table.table-striped.table-bordered.table-condensed.tablesorter#profile_table
50
- %thead
51
- %tr
52
- %th Step
53
- %th Total time
54
- %th Passed
55
- %th Average
56
- %th Fastest
57
- %th Slowest
58
- %th Variation
59
- %th Variance
60
- %th Std Deviation
61
- %th Skipped
62
- %th Error
63
- %th Undef
64
- %th Total count
65
- %tbody
66
- - profile.step_profiles.each do |step, meta|
67
- %tr
68
- %td
69
- %abbr{:title => "#{meta[:regexp]}"} #{step}
70
- %td #{format_ts(meta[:total_duration])}
71
- %td
72
- - if meta[:passed][:count] > 0
73
- %abbr{:title => "#{ format_step_usage(meta[:passed]) }" } #{meta[:passed][:count]}
74
- - else
75
- 0
76
- %td #{format_ts(meta[:average])}
77
- %td #{format_ts(meta[:fastest])}
78
- %td #{format_ts(meta[:slowest])}
79
- %td #{format_ts(meta[:variation])}
80
- %td #{format_ts(meta[:variance])}
81
- %td #{format_ts(meta[:standard_deviation])}
82
- %td
83
- - if meta[:skipped][:count] > 0
84
- %abbr{:title => "#{ format_step_usage(meta[:skipped]) }" } #{meta[:skipped][:count]}
85
- - else
86
- 0
87
- %td
88
- - if meta[:failed][:count] > 0
89
- %abbr{:title => "#{ format_step_usage(meta[:failed]) }" } #{meta[:failed][:count]}
90
- - else
91
- 0
92
- %td
93
- - if meta[:undefined][:count] > 0
94
- %abbr{:title => "#{ format_step_usage(meta[:undefined]) }" } #{meta[:undefined][:count]}
95
- - else
96
- 0
97
- %td #{meta[:total_count]}
50
+ %ul.nav.nav-tabs
51
+ %li.active
52
+ %a{:href => '#steps', :'data-toggle' => 'tab'} Steps
53
+ %li
54
+ %a{:href => '#features', :'data-toggle' => 'tab'} Features
55
+ - if profile.has_unmatched_steps?
56
+ %li
57
+ %a{:href => '#unused_steps', :'data-toggle' => 'tab'} Unused Steps (#{profile.unmatched_steps.count})
58
+ %div.tab-content
59
+ %div.tab-pane.active#steps
60
+ %table.table.table-striped.table-bordered.table-condensed.tablesorter#profile_table
61
+ %thead
62
+ %tr
63
+ %th Step
64
+ %th Total time
65
+ %th Passed
66
+ %th Average
67
+ %th Fastest
68
+ %th Slowest
69
+ %th Variation
70
+ %th Variance
71
+ %th Std Deviation
72
+ %th Skipped
73
+ %th Error
74
+ %th Undef
75
+ %th Total count
76
+ %tbody
77
+ - profile.step_profiles.each do |step, meta|
78
+ %tr
79
+ %td
80
+ %abbr{:title => "#{meta[:regexp]}"} #{step}
81
+ %td #{format_ts(meta[:total_duration])}
82
+ %td
83
+ - if meta[:passed][:count] > 0
84
+ %abbr{:title => "#{ format_step_usage(meta[:passed]) }" } #{meta[:passed][:count]}
85
+ - else
86
+ 0
87
+ %td #{format_ts(meta[:average])}
88
+ %td #{format_ts(meta[:fastest])}
89
+ %td #{format_ts(meta[:slowest])}
90
+ %td #{format_ts(meta[:variation])}
91
+ %td #{format_ts(meta[:variance])}
92
+ %td #{format_ts(meta[:standard_deviation])}
93
+ %td
94
+ - if meta[:skipped][:count] > 0
95
+ %abbr{:title => "#{ format_step_usage(meta[:skipped]) }" } #{meta[:skipped][:count]}
96
+ - else
97
+ 0
98
+ %td
99
+ - if meta[:failed][:count] > 0
100
+ %abbr{:title => "#{ format_step_usage(meta[:failed]) }" } #{meta[:failed][:count]}
101
+ - else
102
+ 0
103
+ %td
104
+ - if meta[:undefined][:count] > 0
105
+ %abbr{:title => "#{ format_step_usage(meta[:undefined]) }" } #{meta[:undefined][:count]}
106
+ - else
107
+ 0
108
+ %td #{meta[:total_count]}
109
+
110
+ %div.tab-pane#features
111
+ %table.table.table-striped.table-bordered.table-condensed#feature_table
112
+ %thead
113
+ %tr
114
+ %th Feature
115
+ %th Total time
116
+ %th Step count
117
+ %th Status
118
+ %th
119
+ %tbody
120
+ - profile.feature_profiles.each do |feature, meta|
121
+ %tr{:class => "#{meta[:status] == :failed ? :danger : meta[:status] == :undefined ? :warning : nil }"}
122
+ %td #{feature}
123
+ %td #{format_ts(meta[:total_duration])}
124
+ %td #{meta[:step_count]}
125
+ %td #{meta[:status]}
126
+ %td
127
+ - if meta[:examples]
128
+ %button.btn.btn-primary{:'data-toggle'=>'modal', :'data-target'=>"##{Digest::MD5.hexdigest(feature)}"}
129
+ #{meta[:example_count]} Examples
130
+
131
+ - profile.feature_profiles.select{|f,m| m.key?(:examples) }.each do |feature, meta|
132
+ %div{ :class=> 'modal fade bs-example-modal-lg', :tabindex=>"-1", :role=>"dialog", :'aria-labelledby'=>"myLargeModalLabel", :'aria-hidden'=>"true", :id => "#{Digest::MD5.hexdigest(feature)}"}
133
+ %div.modal-dialog.modal-lg
134
+ %div.modal-content
135
+ %div.modal-header
136
+ %h4 #{feature}
137
+ %div.modal-body
138
+ %table.table.table-striped.table-bordered.table-condensed
139
+ %thead
140
+ %tr
141
+ %th Example
142
+ %th Total time
143
+ %th Step count
144
+ %th Status
145
+ %tbody
146
+ - meta[:examples].each do |example, meta|
147
+ %tr{:class => "#{meta[:status] == :failed ? :danger : meta[:status] == :undefined ? :warning : nil }"}
148
+ %td #{example}
149
+ %td #{format_ts(meta[:total_duration])}
150
+ %td #{meta[:step_count]}
151
+ %td #{meta[:status]}
152
+ %tfooter
153
+ %tr
154
+ %th
155
+ %th #{format_ts(meta[:total_duration])}
156
+ %th #{meta[:step_count]}
157
+ %th #{meta[:status]}
158
+ %div.modal-footer
159
+ %button.btn.btn-default{:type=>"button", :'data-dismiss'=>"modal"}
160
+ Close
161
+
162
+ - if profile.has_unmatched_steps?
163
+ %div.tab-pane#unused_steps
164
+ %table.table.table-striped.table-bordered.table-condensed.tablesorter#unmatched_steps_table
165
+ %thead
166
+ %tr
167
+ %th Step Location
168
+ %th Step
169
+ %tbody
170
+ - profile.unmatched_steps.each do |step_location, step|
171
+ %tr
172
+ %td #{step_location}
173
+ %td #{step}
174
+
98
175
  %script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"}
99
176
  %script{:src => "http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"}
100
177
  %script{:src => "https://raw.githubusercontent.com/christianbach/tablesorter/master/jquery.tablesorter.min.js"}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cucumber_characteristics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-07 00:00:00.000000000 Z
12
+ date: 2014-06-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: haml
@@ -136,12 +136,16 @@ files:
136
136
  - README.md
137
137
  - Rakefile
138
138
  - cucumber_characteristics.gemspec
139
+ - features/ambiguous.feature
140
+ - features/characteristics/cucumber_step_characteristics.html
141
+ - features/characteristics/cucumber_step_characteristics.json
139
142
  - features/failure.feature
140
143
  - features/pending.feature
141
144
  - features/scenario.feature
142
145
  - features/scenario_outline.feature
143
146
  - features/scenario_outline_with_background.feature
144
147
  - features/scenario_with_background.feature
148
+ - features/step_definitions/ambiguous_steps.rb
145
149
  - features/step_definitions/fail_steps.rb
146
150
  - features/step_definitions/wait_steps.rb
147
151
  - lib/cucumber_characteristics.rb
@@ -179,11 +183,15 @@ signing_key:
179
183
  specification_version: 3
180
184
  summary: Gem to profile cucumber steps and features
181
185
  test_files:
186
+ - features/ambiguous.feature
187
+ - features/characteristics/cucumber_step_characteristics.html
188
+ - features/characteristics/cucumber_step_characteristics.json
182
189
  - features/failure.feature
183
190
  - features/pending.feature
184
191
  - features/scenario.feature
185
192
  - features/scenario_outline.feature
186
193
  - features/scenario_outline_with_background.feature
187
194
  - features/scenario_with_background.feature
195
+ - features/step_definitions/ambiguous_steps.rb
188
196
  - features/step_definitions/fail_steps.rb
189
197
  - features/step_definitions/wait_steps.rb