test-prof 0.4.9 → 0.5.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -20
  3. data/README.md +10 -57
  4. data/assets/tagprof.demo.html +447 -0
  5. data/assets/tagprof.template.html +447 -0
  6. data/lib/minitest/event_prof_formatter.rb +18 -16
  7. data/lib/test_prof.rb +2 -1
  8. data/lib/test_prof/any_fixture.rb +80 -4
  9. data/lib/test_prof/any_fixture/dsl.rb +18 -0
  10. data/lib/test_prof/event_prof.rb +10 -108
  11. data/lib/test_prof/event_prof/custom_events.rb +30 -0
  12. data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
  13. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +1 -1
  14. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +1 -1
  15. data/lib/test_prof/event_prof/minitest.rb +6 -0
  16. data/lib/test_prof/event_prof/profiler.rb +129 -0
  17. data/lib/test_prof/event_prof/rspec.rb +20 -11
  18. data/lib/test_prof/factory_all_stub.rb +32 -0
  19. data/lib/test_prof/factory_all_stub/factory_bot_patch.rb +13 -0
  20. data/lib/test_prof/factory_doctor/rspec.rb +3 -2
  21. data/lib/test_prof/factory_prof/printers/flamegraph.rb +9 -13
  22. data/lib/test_prof/recipes/active_record_shared_connection.rb +55 -0
  23. data/lib/test_prof/recipes/logging.rb +37 -0
  24. data/lib/test_prof/recipes/rspec/any_fixture.rb +4 -1
  25. data/lib/test_prof/recipes/rspec/factory_all_stub.rb +10 -0
  26. data/lib/test_prof/rspec_dissect/rspec.rb +4 -2
  27. data/lib/test_prof/rspec_stamp/rspec.rb +3 -2
  28. data/lib/test_prof/tag_prof.rb +4 -0
  29. data/lib/test_prof/tag_prof/printers/html.rb +24 -0
  30. data/lib/test_prof/tag_prof/printers/simple.rb +82 -0
  31. data/lib/test_prof/tag_prof/result.rb +38 -0
  32. data/lib/test_prof/tag_prof/rspec.rb +43 -40
  33. data/lib/test_prof/utils/html_builder.rb +21 -0
  34. data/lib/test_prof/version.rb +1 -1
  35. metadata +20 -24
  36. data/assets/logo.svg +0 -1
  37. data/assets/testprof.png +0 -0
  38. data/guides/.rubocop.yml +0 -1
  39. data/guides/any_fixture.md +0 -114
  40. data/guides/before_all.md +0 -98
  41. data/guides/event_prof.md +0 -177
  42. data/guides/factory_default.md +0 -111
  43. data/guides/factory_doctor.md +0 -119
  44. data/guides/factory_prof.md +0 -86
  45. data/guides/let_it_be.md +0 -97
  46. data/guides/rspec_dissect.md +0 -60
  47. data/guides/rspec_stamp.md +0 -52
  48. data/guides/rubocop.md +0 -48
  49. data/guides/ruby_prof.md +0 -63
  50. data/guides/stack_prof.md +0 -47
  51. data/guides/tag_prof.md +0 -51
  52. data/guides/tests_sampling.md +0 -24
@@ -1,98 +0,0 @@
1
- # Before All
2
-
3
- Rails has a great feature – `transactional_tests`, i.e. running each example within a transaction which is roll-backed in the end.
4
-
5
- Thus no example polutes global database state.
6
-
7
- But what if have a lot of examples with a common setup?
8
-
9
- Of course, we can do something like this:
10
-
11
- ```ruby
12
- describe BeatleWeightedSearchQuery do
13
- before(:each) do
14
- @paul = create(:beatle, name: 'Paul')
15
- @ringo = create(:beatle, name: 'Ringo')
16
- @george = create(:beatle, name: 'George')
17
- @john = create(:beatle, name: 'John')
18
- end
19
-
20
- # and about 15 examples here
21
- end
22
- ```
23
-
24
- Or you can try `before(:all)`:
25
-
26
- ```ruby
27
- describe BeatleWeightedSearchQuery do
28
- before(:all) do
29
- @paul = create(:beatle, name: 'Paul')
30
- # ...
31
- end
32
-
33
- # ...
34
- end
35
- ```
36
-
37
- But then you have to deal with database cleaning, which can be either tricky or slow.
38
-
39
- There is a better option: we can wrap the whole example group into a transaction.
40
- And that's how `before_all` works:
41
-
42
- ```ruby
43
- describe BeatleWeightedSearchQuery do
44
- before_all do
45
- @paul = create(:beatle, name: 'Paul')
46
- # ...
47
- end
48
-
49
- # ...
50
- end
51
- ```
52
-
53
- That's all!
54
-
55
- ## Instructions
56
-
57
- In your `spec_helper.rb`:
58
-
59
- ```ruby
60
- require 'test_prof/recipes/rspec/before_all'
61
- ```
62
-
63
- ## Caveats
64
-
65
- If you modify objects generated within a `before_all` block in your examples, you maybe have to re-initiate them:
66
-
67
-
68
- ```ruby
69
- before_all do
70
- @user = create(:user)
71
- end
72
-
73
- let(:user) { @user }
74
-
75
- it 'when user is admin' do
76
- # we modified our object in-place!
77
- user.update!(role: 1)
78
- expect(user).to be_admin
79
- end
80
-
81
- it 'when user is regular' do
82
- # now @user's state depends on the order of specs!
83
- expect(user).not_to be_admin
84
- end
85
- ```
86
-
87
- The easiest way to solve this is to reload record for every example (it's still much faster than creating a new one):
88
-
89
-
90
- ```ruby
91
- before_all do
92
- @user = create(:user)
93
- end
94
-
95
- # Note, that @user.reload may not be enough,
96
- # 'cause it doesn't reset associations
97
- let(:user) { User.find(@user.id) }
98
- ```
@@ -1,177 +0,0 @@
1
- # EventProf
2
-
3
- EventProf collects instrumentation (such as ActiveSupport::Notifications) metrics during your test suite run.
4
-
5
- It works very similar to `rspec --profile` but can track arbitrary events.
6
-
7
- Example output:
8
-
9
- ```sh
10
- [TEST PROF INFO] EventProf results for sql.active_record
11
-
12
- Total time: 00:00.256
13
- Total events: 1031
14
-
15
- Top 5 slowest suites (by time):
16
-
17
- AnswersController (./spec/controllers/answers_controller_spec.rb:3) – 00:00.119 (549 / 20)
18
- QuestionsController (./spec/controllers/questions_controller_spec.rb:3) – 00:00.105 (360 / 18)
19
- CommentsController (./spec/controllers/comments_controller_spec.rb:3) – 00:00.032 (122 / 4)
20
-
21
- Top 5 slowest tests (by time):
22
-
23
- destroys question (./spec/controllers/questions_controller_spec.rb:38) – 00:00.022 (29)
24
- change comments count (./spec/controllers/comments_controller_spec.rb:7) – 00:00.011 (34)
25
- change Votes count (./spec/shared_examples/controllers/voted_examples.rb:23) – 00:00.008 (25)
26
- change Votes count (./spec/shared_examples/controllers/voted_examples.rb:23) – 00:00.008 (32)
27
- fails (./spec/shared_examples/controllers/invalid_examples.rb:3) – 00:00.007 (34)
28
-
29
- ```
30
-
31
- ## Instructions
32
-
33
- Currently, EventProf supports only ActiveSupport::Notifications
34
-
35
- To activate EventProf with:
36
-
37
- ### RSpec
38
-
39
- Use `EVENT_PROF` environment variable set to event name:
40
-
41
- ```sh
42
- # Collect SQL queries stats for every suite and example
43
- EVENT_PROF='sql.active_record' rspec ...
44
- ```
45
-
46
- ### Minitest
47
-
48
- Use `EVENT_PROF` environment variable set to event name:
49
-
50
- ```sh
51
- # Collect SQL queries stats for every suite and example
52
- EVENT_PROF='sql.active_record' rake test
53
- ```
54
- or use CLI options as well:
55
-
56
- ```sh
57
- # Run a specific file using CLI option
58
- ruby test/my_super_test.rb --event-prof=sql.active_record
59
-
60
- # Show the list of possible options:
61
- ruby test/my_super_test.rb --help
62
- ```
63
-
64
- ### Using with Minitest::Reporters
65
-
66
- If you're using `Minitest::Reporters` in your project you have to explicitly declare it
67
- in your test helper file:
68
-
69
- ```sh
70
- require 'minitest/reporters'
71
- Minitest::Reporters.use! [YOUR_FAVORITE_REPORTERS]
72
- ```
73
- #### NOTICE
74
- When you have `minitest-reporters` installed as a gem but not declared in your `Gemfile`
75
- make sure to always prepend your test run command with `bundle exec` (but we sure that you always do it).
76
- Otherwise, you'll get an error caused by Minitest plugin system, which scans all the entries in the
77
- `$LOAD_PATH` for any `minitest/*_plugin.rb`, thus initialization of `minitest-reporters` plugin which is
78
- available in that case doesn't happens correctly.
79
-
80
- See [Rails guides](http://guides.rubyonrails.org/active_support_instrumentation.html)
81
- for the list of available events if you're using Rails.
82
-
83
- If you're using [rom-rb](http://rom-rb.org) you might be interested in profiling `'sql.rom'` event.
84
-
85
- ### Configuration
86
-
87
- By default, EventProf collects information only about top-level groups (aka suites),
88
- but you can also profile individual examples. Just set the configuration option:
89
-
90
- ```ruby
91
- TestProf::EventProf.configure do |config|
92
- config.per_example = true
93
- end
94
- ```
95
-
96
- Or provide the `EVENT_PROF_EXAMPLES=1` env variable.
97
-
98
- Another useful configuration parameter – `rank_by`. It's responsible for sorting stats –
99
- either by the time spent in the event or by the number of occurrences:
100
-
101
- ```sh
102
- EVENT_PROF_RANK=count EVENT_PROF='instantiation.active_record' be rspec
103
- ```
104
-
105
- See [event_prof.rb](https://github.com/palkan/test-prof/tree/master/lib/test_prof/event_prof.rb) for all available configuration options and their usage.
106
-
107
- ## Using with RSpecStamp
108
-
109
- EventProf can be used with [RSpec Stamp](https://github.com/palkan/test-prof/tree/master/guides/rspec_stamp.md) to automatically mark _slow_ examples with custom tags. For example:
110
-
111
- ```sh
112
- EVENT_PROF="sql.active_record" EVENT_PROF_STAMP="slow:sql" rspec ...
113
- ```
114
-
115
- After running the command above the slowest example groups (and examples if configured) would be marked with the `slow: :sql` tag.
116
-
117
- ## Custom Instrumentation
118
-
119
- To use EventProf with your instrumentation engine just complete the two following steps:
120
-
121
- - Add a wrapper for your instrumentation:
122
-
123
-
124
- ```ruby
125
- # Wrapper over your instrumentation
126
- module MyEventsWrapper
127
- # Should contain the only one method
128
- def self.subscribe(event)
129
- raise ArgumentError, 'Block is required!' unless block_given?
130
-
131
- ::MyEvents.subscribe(event) do |start, finish, *|
132
- yield (finish - start)
133
- end
134
- end
135
- end
136
- ```
137
-
138
- - Set instrumenter in the config:
139
-
140
-
141
- ```ruby
142
- TestProf::EventProf.configure do |config|
143
- config.instrumenter = MyEventsWrapper
144
- end
145
- ```
146
-
147
- ## Custom Events
148
-
149
- ### `"factory.create"`
150
-
151
- FactoryGirl provides its own instrumentation ('factory_girl.run_factory'); but there is a caveat – it fires an event every time a factory is used, even when we use factory for nested associations. Thus it's not possible to calculate the total time spent in factories due to the double calculation.
152
-
153
- EventProf comes with a little patch for FactoryGirl which provides instrumentation only for top-level `FactoryGirl.create` calls. It is loaded automatically if you use `"factory.create"` event:
154
-
155
- ```sh
156
- EVENT_PROF=factory.create bundle exec rspec
157
- ```
158
-
159
- ### `"sidekiq.jobs"`
160
-
161
- Collects statistics about Sidekiq jobs that have been run inline:
162
-
163
- ```sh
164
- EVENT_PROF=sidekiq.jobs bundle exec rspec
165
- ```
166
-
167
- **NOTE**: automatically sets `rank_by` to `count` ('cause it doesn't make sense to collect the information about time spent – see below).
168
-
169
- ### `"sidekiq.inline"`
170
-
171
- Collects statistics about Sidekiq jobs that have been run inline (excluding nested jobs):
172
-
173
- ```sh
174
- EVENT_PROF=sidekiq.inline bundle exec rspec
175
- ```
176
-
177
- Use this event to profile the time spent running Sidekiq jobs.
@@ -1,111 +0,0 @@
1
- # FactoryDefault
2
-
3
- _Factory Default_ aims to help you cope with _factory cascades_ (see [FactoryProf](https://github.com/palkan/test-prof/tree/master/guides/factory_prof.md)) by reusing associated records.
4
-
5
- **NOTE**. Only works with FactoryGirl/FactoryBot.
6
-
7
- It can be very useful when you're working on a typical SaaS application (or other hierarchical data).
8
-
9
- Consider an example. Assume we have the following factories:
10
-
11
- ```ruby
12
- factory :account do
13
- end
14
-
15
- factory :user do
16
- account
17
- end
18
-
19
- factory :project do
20
- account
21
- user
22
- end
23
-
24
- factory :task do
25
- account
26
- project
27
- user
28
- end
29
- ```
30
-
31
- And we want to test the `Task` model:
32
-
33
- ```ruby
34
- describe 'PATCH #update' do
35
- let(:task) { create(:task) }
36
-
37
- it 'works' do
38
- patch :update, id: task.id, task: { completed: 't' }
39
- expect(response).to be_success
40
- end
41
-
42
- # ...
43
- end
44
- ```
45
-
46
- How many users and accounts are created per example? Two and four respectively.
47
-
48
- And it breaks our logic (every object should belong to the same account).
49
-
50
- Typical workaround:
51
-
52
- ```ruby
53
- describe 'PATCH #update' do
54
- let(:account) { create(:account) }
55
- let(:project) { create(:project, account: account) }
56
- let(:task) { create(:task, project: project, account: account) }
57
-
58
- it 'works' do
59
- patch :update, id: task.id, task: { completed: 't' }
60
- expect(response).to be_success
61
- end
62
- end
63
- ```
64
-
65
- That works. And there are some cons: it's a little bit verbose and error-prone (easy to forget something).
66
-
67
- Here is how we can deal with it using FactoryDefault:
68
-
69
- ```ruby
70
- describe 'PATCH #update' do
71
- let(:account) { create_default(:account) }
72
- let(:project) { create_default(:project) }
73
- let(:task) { create(:task) }
74
-
75
- # and if we need more projects, users, tasks with the same parent record,
76
- # we just write
77
- let(:another_project) { create(:project) } # uses the same account
78
- let(:another_task) { create(:task) } # uses the same account
79
-
80
- it 'works' do
81
- patch :update, id: task.id, task: { completed: 't' }
82
- expect(response).to be_success
83
- end
84
- end
85
- ```
86
-
87
- **NOTE**. This feature introduces a bit of _magic_ to your tests, so use it with caution ('cause tests should be human-readable first). Good idea is to use defaults for top-level entities only (such as tenants in multi-tenancy apps).
88
-
89
- ## Instructions
90
-
91
- In your `spec_helper.rb`:
92
-
93
- ```ruby
94
- require 'test_prof/recipes/rspec/factory_default'
95
- ```
96
-
97
- This adds two new methods to FactoryBot:
98
-
99
- - `FactoryBot#set_factory_default(factory, object)` – use the `object` as default for associations built with `factory`
100
-
101
- Example:
102
-
103
- ```ruby
104
- let(:user) { create(:user) }
105
-
106
- before { FactoryBot.set_factory_default(:user, user) }
107
- ```
108
-
109
- - `FactoryBot#create_default(factory, *args)` – is a shortcut for `create` + `set_factory_default`.
110
-
111
- **NOTE**. Defaults are cleaned up after each example.
@@ -1,119 +0,0 @@
1
- # Factory Doctor
2
-
3
- One common bad pattern that slows our tests down is unnecessary database manipulation. Consider a _bad_ example:
4
-
5
- ```ruby
6
- it 'validates name presence' do
7
- user = create(:user)
8
- user.name = ''
9
- expect(user).not_to be_valid
10
- end
11
- ```
12
-
13
- Here we create a new user record, run all callbacks and validations and save it to the database. We don't need all these! Here is a _good_ example:
14
-
15
- ```ruby
16
- it 'validates name presence' do
17
- user = build_stubbed(:user)
18
- user.name = ''
19
- expect(user).not_to be_valid
20
- end
21
- ```
22
-
23
- Read more about [`build_stubbed`](https://robots.thoughtbot.com/use-factory-girls-build-stubbed-for-a-faster-test).
24
-
25
- FactoryDoctor is a tool that helps you identify such _bad_ tests, i.e. tests that perform unnecessary database queries.
26
-
27
- Example output:
28
-
29
- ```sh
30
- [TEST PROF INFO] FactoryDoctor report
31
-
32
- Total (potentially) bad examples: 2
33
- Total wasted time: 00:13.165
34
-
35
- User (./spec/models/user_spec.rb:3)
36
- validates name (./spec/user_spec.rb:8) – 1 record created, 00:00.114
37
- validates email (./spec/user_spec.rb:8) – 2 records created, 00:00.514
38
- ```
39
-
40
- **NOTE**: have you noticed the "potentially" word? Unfortunately, FactoryDoctor is not a
41
- magician (it's still learning) and sometimes it produces false negatives and false positives too.
42
-
43
- Please, submit an [issue](https://github.com/palkan/test-prof/issues) if you found a case which makes FactoryDoctor fail.
44
-
45
- You can also tell FactoryDoctor to ignore specific examples/groups. Just add the `:fd_ignore` tag to it:
46
-
47
- ```ruby
48
- # won't be reported as offense
49
- it 'is ignored', :fd_ignore do
50
- user = create(:user)
51
- user.name = ''
52
- expect(user).not_to be_valid
53
- end
54
- ```
55
-
56
- ## Instructions
57
-
58
- Currently, FactoryDoctor works only with FactoryGirl/FactoryBot.
59
-
60
- ## RSpec
61
-
62
- To activate FactoryDoctor use `FDOC` environment variable:
63
-
64
- ```sh
65
- FDOC=1 rspec ...
66
- ```
67
-
68
- ## Using with RSpecStamp
69
-
70
- FactoryDoctor can be used with [RSpec Stamp](https://github.com/palkan/test-prof/tree/master/guides/rspec_stamp.md) to automatically mark _bad_ examples with custom tags. For example:
71
-
72
- ```sh
73
- FDOC=1 FDOC_STAMP="fdoc:consider" rspec ...
74
- ```
75
-
76
- After running the command above all _potentially_ bad examples would be marked with the `fdoc: :consider` tag.
77
-
78
- ## Minitest
79
-
80
- To activate FactoryDoctor use `FDOC` environment variable:
81
-
82
- ```sh
83
- FDOC=1 ruby ...
84
- ```
85
-
86
- or use CLI option as shown below:
87
-
88
- ```sh
89
- ruby ... --factory-doctor
90
- ```
91
-
92
- The same option to force Factory Doctor to ignore specific examples is also available for Minitest.
93
- Just use `fd_ignore` inside your example:
94
-
95
- ```ruby
96
- # won't be reported as offense
97
- it 'is ignored' do
98
- fd_ignore
99
-
100
- @user.name = ''
101
- refute @user.valid?
102
- end
103
- ```
104
-
105
- ## Using with Minitest::Reporters
106
-
107
- If you're using `Minitest::Reporters` in your project you have to explicitly declare it
108
- in your test helper file:
109
-
110
- ```sh
111
- require 'minitest/reporters'
112
- Minitest::Reporters.use! [YOUR_FAVORITE_REPORTERS]
113
- ```
114
- #### NOTICE
115
- When you have `minitest-reporters` installed as a gem but not declared in your `Gemfile`
116
- make sure to always prepend your test run command with `bundle exec` (but we sure that you always do it).
117
- Otherwise, you'll get an error caused by Minitest plugin system, which scans all the entries in the
118
- `$LOAD_PATH` for any `minitest/*_plugin.rb`, thus initialization of `minitest-reporters` plugin which is
119
- available in that case doesn't happens correctly.