test-prof 0.4.9 → 0.5.0.pre1

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 (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.