factory_inspector 0.0.1
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.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +31 -0
- data/LICENSE +22 -0
- data/README.md +85 -0
- data/Rakefile +2 -0
- data/factory_inspector.gemspec +22 -0
- data/lib/factory_inspector/report.rb +50 -0
- data/lib/factory_inspector/version.rb +3 -0
- data/lib/factory_inspector.rb +58 -0
- data/spec/report_spec.rb +63 -0
- metadata +108 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
factory_inspector (0.0.1)
|
5
|
+
activesupport
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (3.2.3)
|
11
|
+
i18n (~> 0.6)
|
12
|
+
multi_json (~> 1.0)
|
13
|
+
diff-lcs (1.1.3)
|
14
|
+
i18n (0.6.0)
|
15
|
+
multi_json (1.3.4)
|
16
|
+
rspec (2.9.0)
|
17
|
+
rspec-core (~> 2.9.0)
|
18
|
+
rspec-expectations (~> 2.9.0)
|
19
|
+
rspec-mocks (~> 2.9.0)
|
20
|
+
rspec-core (2.9.0)
|
21
|
+
rspec-expectations (2.9.1)
|
22
|
+
diff-lcs (~> 1.1.3)
|
23
|
+
rspec-mocks (2.9.0)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler (>= 1.0.0)
|
30
|
+
factory_inspector!
|
31
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 David Kennedy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# FactoryInspector
|
2
|
+
|
3
|
+
This very simple gem reports on how FactoryGirl factories
|
4
|
+
are being used during your test runs. This is useful in
|
5
|
+
understanding where the time is going during your test
|
6
|
+
runs - while FactoryGirl is useful, overuse can lead to
|
7
|
+
serious slowdowns to a cascade of database writes when
|
8
|
+
building a test object.
|
9
|
+
|
10
|
+
This is a developer's tool; the Gem makes no effort to
|
11
|
+
sanitize inputs or help you avoid making mistakes. It
|
12
|
+
relies on the changes brought in with FactoryGirl 3.2:
|
13
|
+
http://robots.thoughtbot.com/post/21719164760/factorygirl-3-2-so-awesome-it-needs-to-be-released
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'factory_inspector'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install factory_inspector
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
FactoryInspector's API is just two methods - `start_inspection` and `generate_report`.
|
32
|
+
|
33
|
+
Let's take a hypothetical `spec/spec_helper.rb` on a RSpec based
|
34
|
+
project; the changes to use FactoryInspector would be:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'factory_inspector'
|
38
|
+
|
39
|
+
# From a project relative filename like 'log/filename.txt'
|
40
|
+
# generate the full path.
|
41
|
+
# TODO There must be simpler way to do this?
|
42
|
+
def get_project_path_for(filename)
|
43
|
+
"#{File.dirname(__FILE__)}/../#{filename}"
|
44
|
+
end
|
45
|
+
|
46
|
+
factory_inspector = FactoryInspector.new
|
47
|
+
RSpec.configure do |config|
|
48
|
+
|
49
|
+
config.before :suite do
|
50
|
+
factory_inspector.start_inspection
|
51
|
+
end
|
52
|
+
|
53
|
+
config.after :suite do
|
54
|
+
report_name = 'log/factory_inspector_report.txt'
|
55
|
+
report_path = get_project_path_for report_name
|
56
|
+
factory_inspector.generate_report report_path
|
57
|
+
puts "Factory Inspector report in '#{report_name}'"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
After the tests have run, the nominated log file will have output
|
63
|
+
similar to this:
|
64
|
+
|
65
|
+
```
|
66
|
+
FACTORY INSPECTOR - 46 FACTORIES USED
|
67
|
+
FACTORY NAME TOTAL OVERALL TIME PER LONGEST STRATEGIES
|
68
|
+
CALLS TIME (s) CALL (s) CALL (s)
|
69
|
+
school_with_terms 1 0.4783 0.47827 0.4783 [:create]
|
70
|
+
school_with_terms_and_cla 5 2.3859 0.47718 0.5184 [:create]
|
71
|
+
school_leaver 1 0.2581 0.25808 0.2581 [:create]
|
72
|
+
pre_enrolled_pupil 1 0.2570 0.25704 0.2570 [:create]
|
73
|
+
pupil_school_enrolment_fo 1 0.2008 0.20075 0.2008 [:build]
|
74
|
+
announcement 5 0.8973 0.17946 0.2327 [:create]
|
75
|
+
sub_class_with_pupils 5 0.7961 0.15921 0.2163 [:create, :build]
|
76
|
+
etc
|
77
|
+
```
|
78
|
+
|
79
|
+
## Contributing
|
80
|
+
|
81
|
+
1. Fork it
|
82
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
83
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
84
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
85
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/factory_inspector/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["David Kennedy"]
|
6
|
+
gem.email = ["david.kennedy@examtime.com"]
|
7
|
+
gem.description = %q{This very simple gem generates reports on how FactoryGirl factories are being used in your test runs.}
|
8
|
+
gem.summary = %q{Reports on how FactoryGirl is used in test runs.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "factory_inspector"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = FactoryInspector::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'activesupport'
|
19
|
+
|
20
|
+
gem.add_development_dependency 'bundler', '>= 1.0.0'
|
21
|
+
gem.add_development_dependency 'rspec'
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module FactoryInspector
|
2
|
+
|
3
|
+
# Report on how a FactoryGirl Factory was used in a test run.
|
4
|
+
# Holds simple metrics and can be updated with new calls.
|
5
|
+
class Report
|
6
|
+
|
7
|
+
attr_reader :factory_name,
|
8
|
+
:calls,
|
9
|
+
:worst_time_in_seconds,
|
10
|
+
:total_time_in_seconds,
|
11
|
+
:strategies
|
12
|
+
|
13
|
+
def initialize(factory_name)
|
14
|
+
@factory_name = factory_name
|
15
|
+
@calls = 0
|
16
|
+
@worst_time_in_seconds = 0
|
17
|
+
@total_time_in_seconds = 0
|
18
|
+
@strategies = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def time_per_call_in_seconds
|
22
|
+
return 0 if @calls == 0
|
23
|
+
@total_time_in_seconds / @calls
|
24
|
+
end
|
25
|
+
|
26
|
+
# Update this report with a new call
|
27
|
+
# * [time] The time taken, in seconds, to call the factory
|
28
|
+
# * [strategy] The strategy used by the factory
|
29
|
+
def update(time, strategy)
|
30
|
+
record_call
|
31
|
+
record_time time
|
32
|
+
if not @strategies.include? strategy
|
33
|
+
@strategies << strategy
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def record_call
|
40
|
+
@calls += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def record_time(time)
|
44
|
+
@worst_time_in_seconds = time if time > @worst_time_in_seconds
|
45
|
+
@total_time_in_seconds += time
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'factory_inspector/version'
|
2
|
+
require 'factory_inspector/report'
|
3
|
+
require 'active_support/notifications'
|
4
|
+
|
5
|
+
module FactoryInspector
|
6
|
+
|
7
|
+
def self.new
|
8
|
+
Inspector.new
|
9
|
+
end
|
10
|
+
|
11
|
+
class Inspector
|
12
|
+
|
13
|
+
def start_inspection
|
14
|
+
@reports = {}
|
15
|
+
ActiveSupport::Notifications.subscribe('factory_girl.run_factory') do |name, start_time, finish_time, id, payload|
|
16
|
+
analyze(payload[:name], start_time, finish_time, payload[:strategy])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_report(output_filename)
|
21
|
+
file = File.open(output_filename, 'w')
|
22
|
+
|
23
|
+
file.write "FACTORY INSPECTOR - #{@reports.values.size} FACTORIES USED\n"
|
24
|
+
file.write " FACTORY NAME TOTAL OVERALL TIME PER LONGEST STRATEGIES\n"
|
25
|
+
file.write " CALLS TIME (s) CALL (s) CALL (s) \n"
|
26
|
+
@reports.sort_by{ |name,report| report.time_per_call_in_seconds }.reverse.each do |report_name, report|
|
27
|
+
line = sprintf(" %-25.25s % 5.0d %5.4f %5.5f %5.4f %s\n",
|
28
|
+
report.factory_name,
|
29
|
+
report.calls,
|
30
|
+
report.total_time_in_seconds,
|
31
|
+
report.time_per_call_in_seconds,
|
32
|
+
report.worst_time_in_seconds,
|
33
|
+
report.strategies)
|
34
|
+
file.write(line)
|
35
|
+
end
|
36
|
+
|
37
|
+
file.close
|
38
|
+
end
|
39
|
+
|
40
|
+
# Callback for use by ActiveSupport::Notifications, not for end
|
41
|
+
# user use directly though it has to be public for ActiveSupport
|
42
|
+
# to see it.
|
43
|
+
#
|
44
|
+
# * [factory_name] Factory name
|
45
|
+
# * [start_time] The start time of the factory call
|
46
|
+
# * [finish_time] The finish time of the factory call
|
47
|
+
# * [strategy] The strategy used when calling the factory
|
48
|
+
#
|
49
|
+
def analyze(factory_name, start_time, finish_time, strategy)
|
50
|
+
if not @reports.has_key? factory_name
|
51
|
+
@reports[factory_name] = FactoryInspector::Report.new(factory_name)
|
52
|
+
end
|
53
|
+
@reports[factory_name].update(finish_time - start_time, strategy)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/spec/report_spec.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'factory_inspector/report'
|
2
|
+
|
3
|
+
describe FactoryInspector::Report do
|
4
|
+
|
5
|
+
let(:foo) { 'FooFactory' }
|
6
|
+
|
7
|
+
context 'on construction' do
|
8
|
+
before :all do
|
9
|
+
@report = FactoryInspector::Report.new(:foo)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should be named for the factory' do
|
13
|
+
@report.factory_name.should == :foo
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should have recorded zero calls' do
|
17
|
+
@report.calls.should == 0
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should have a zero worst time' do
|
21
|
+
@report.worst_time_in_seconds.should == 0
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should have a zero total time' do
|
25
|
+
@report.total_time_in_seconds.should == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should have recorded no strategies' do
|
29
|
+
@report.strategies.should be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should have a zero time-per-call' do
|
33
|
+
@report.time_per_call_in_seconds.should == 0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "on update" do
|
38
|
+
before :all do
|
39
|
+
@report = FactoryInspector::Report.new(:foo)
|
40
|
+
@report.update(3, 'build')
|
41
|
+
@report.update(5, 'create')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should have incremented the call count' do
|
45
|
+
@report.calls.should == 2
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should have recorded the total time' do
|
49
|
+
@report.total_time_in_seconds.should == 8
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should report the time per call' do
|
53
|
+
@report.total_time_in_seconds.should == 8
|
54
|
+
@report.calls.should == 2
|
55
|
+
@report.time_per_call_in_seconds.should == 4
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should report the strategies used' do
|
59
|
+
@report.strategies.should == ['build', 'create']
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: factory_inspector
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Kennedy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.0.0
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: This very simple gem generates reports on how FactoryGirl factories are
|
63
|
+
being used in your test runs.
|
64
|
+
email:
|
65
|
+
- david.kennedy@examtime.com
|
66
|
+
executables: []
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- .rspec
|
72
|
+
- .rvmrc
|
73
|
+
- Gemfile
|
74
|
+
- Gemfile.lock
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
- Rakefile
|
78
|
+
- factory_inspector.gemspec
|
79
|
+
- lib/factory_inspector.rb
|
80
|
+
- lib/factory_inspector/report.rb
|
81
|
+
- lib/factory_inspector/version.rb
|
82
|
+
- spec/report_spec.rb
|
83
|
+
homepage: ''
|
84
|
+
licenses: []
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.8.24
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Reports on how FactoryGirl is used in test runs.
|
107
|
+
test_files:
|
108
|
+
- spec/report_spec.rb
|