test_data 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84a9fc3fe7b3dda0cf1ebdc775c9191757d21de11e033189c6450b91f63332e7
4
- data.tar.gz: 0a47179f0607d9c57f63706e35de176b14d93989c6e5406094524a0fb3b09704
3
+ metadata.gz: 18d3469e9d0173f81ef3b2d8d082861094d79354b8fb544e983bdcfdd8eb788b
4
+ data.tar.gz: 983a1645132b22024799670ec463f11fe5f8e0d1b9427b96940dea8c684fbafb
5
5
  SHA512:
6
- metadata.gz: 4c10de3f9e234ae53e47553d099612dc7474cb3e98022b6a953d60beb8da3f502b00f506f29729dad0a9eb37c8e7f05a662263963300d7c531f48fef660c9173
7
- data.tar.gz: d718e34070ca15bd9d0e27c75789e170e42da183fac8efc973404d9cea909ce984453f9393277b37ad36ff42fa4866c66de14d2a82a149ae536f0555c2f5f52d
6
+ metadata.gz: c7fb1b86f23f074bb185cb1cf11c437d4ba57445d65907509384838993628c5759803ee90f3f290dbb219754806bb1c1133b7e84bf430918c1a23b3fca86b752
7
+ data.tar.gz: d00e512b415c74ab1650a3b3d58e36759d085056375e07326d8790d0033bc18066a2d82c791a578fb9d11cb7c08f9056d4d5e380b9cc52f97dd467c245469771
data/CHANGELOG.md CHANGED
@@ -1,4 +1,9 @@
1
- # unreleased
1
+ # 0.2.2
2
+
3
+ - Improve performance of Rails fixtures being repeatedly loaded by changing the
4
+ caching strategy
5
+
6
+ # 0.2.1
2
7
 
3
8
  - Adds several lifecycle hooks:
4
9
  - config.after_test_data_load
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- test_data (0.2.1)
4
+ test_data (0.2.2)
5
5
  railties (~> 6.0)
6
6
 
7
7
  GEM
@@ -34,14 +34,14 @@ GEM
34
34
  erubi (1.10.0)
35
35
  i18n (1.8.10)
36
36
  concurrent-ruby (~> 1.0)
37
- loofah (2.10.0)
37
+ loofah (2.11.0)
38
38
  crass (~> 1.0.2)
39
39
  nokogiri (>= 1.5.9)
40
40
  method_source (1.0.0)
41
- mini_portile2 (2.5.3)
41
+ mini_portile2 (2.6.1)
42
42
  minitest (5.14.4)
43
- nokogiri (1.11.7)
44
- mini_portile2 (~> 2.5.0)
43
+ nokogiri (1.12.0)
44
+ mini_portile2 (~> 2.6.1)
45
45
  racc (~> 1.4)
46
46
  parallel (1.20.1)
47
47
  parser (3.0.1.0)
data/README.md CHANGED
@@ -375,17 +375,30 @@ RSpec.describe "Kitchen sink", type: :request do
375
375
  end
376
376
  ```
377
377
 
378
+ But wait, there's more! If your test suite switches between multiple modes from
379
+ test-to-test, it's important to be aware of the marginal cost _between_ each of
380
+ those tests. For example, two tests in a row that call `TestData.uses_test_data`
381
+ only need a simple rollback as test setup, but a `TestData.uses_test_data`
382
+ followed by a `TestData.uses_clean_slate` requires a rollback, a truncation, and
383
+ another savepoint. These small costs add up, so consider [speeding up your
384
+ build](#im-worried-my-tests-arent-as-fast-as-they-should-be) by grouping your
385
+ tests into sub-suites based on their source of test data.
386
+
378
387
  #### If your situation is more complicated
379
388
 
380
389
  If you're adding `test_data` to an existing application, it's likely that you
381
390
  won't be able to easily adopt a one-size-fits-all approach to test setup across
382
391
  your entire suite. Some points of reference, if that's the situation you're in:
383
392
 
384
- * If your test suite is already using fixtures or factories and the above hooks
385
- just broke everything, check out our [interoperability
393
+ * If your test suite is **already using fixtures or factories** and the above
394
+ hooks just broke everything, check out our [interoperability
386
395
  guide](#factory--fixture-interoperability-guide) for help.
387
- * If you don't want `test_data` managing transactions and cleanup for you and
388
- just want to load the SQL dump, you can call
396
+ * If you need to make any changes to the data after it's loaded, truncated, or
397
+ after Rails fixtures are loaded, you can configure [lifecycle
398
+ hooks](#lifecycle-hooks) that will help you achieve a **very fast test suite**
399
+ by including those changes inside the transaction savepoints
400
+ * If you **don't want `test_data` managing transactions** and cleanup for you
401
+ and just want to load the SQL dump, you can call
389
402
  [TestData.insert_test_data_dump](#testdatainsert_test_data_dump)
390
403
  * For more information on how all this works, see the [API
391
404
  reference](#api-reference).
@@ -489,15 +502,15 @@ suite after following the initial setup guide and see if the suite just passes.
489
502
 
490
503
  If you find that your test suite is failing after adding
491
504
  `TestData.uses_test_data` to your setup, don't panic! Test failures are most
492
- likely caused by the combination of your `test_data` database with the data
493
- persisted by your factories.
505
+ likely caused by the combination of your `test_data` SQL dump with the records
506
+ inserted by your factories.
494
507
 
495
508
  One approach would be to attempt to resolve each such failure one-by-one—usually
496
509
  by updating the offending factories or editing your `test_data` database to
497
510
  ensure they steer clear of one another. Care should be taken to preserve the
498
- conceptual encapsulation of each test, however, as naively squashing errors can
499
- introduce inadvertent coupling between your factories and your `test_data`
500
- database such that neither can be used independently of the other.
511
+ conceptual encapsulation of each test, however, as naively squashing errors
512
+ risks introducing inadvertent coupling between your factories and your
513
+ `test_data` data such that neither can be used independently of the other.
501
514
 
502
515
  Another approach that the `test_data` gem provides is an additional mode with
503
516
  `TestData.uses_clean_slate`, which—when called at the top of a factory-dependent
@@ -1203,8 +1216,113 @@ run. That said—and especially if you're adding `test_data` to an existing test
1203
1216
  suite—care should be taken to audit everything the suite does between tests in
1204
1217
  order to optimize its overall runtime.
1205
1218
 
1206
- The first and most likely source of unnecessary slowness is redundant test
1207
- cleanup—the speed gained from sandwiching every expensive operation between
1219
+ #### Randomized test order leading to data churn
1220
+
1221
+ Generally speaking, randomizing the order in which tests run is an unmitigated
1222
+ win: randomizing helps you catch any unintended dependency between two tests
1223
+ early, when it's still cheap & easy to fix. However, if your tests use different
1224
+ sources of test data (e.g. some call `TestData.uses_test_data` and some call
1225
+ `TestData.uses_clean_slate`), it's very likely that randomizing your tests will
1226
+ result in a significantly slower overall test suite. Instead, if you group tests
1227
+ that use the same type of test data together (e.g. by separating them into
1228
+ separate suites), you might find profound speed gains.
1229
+
1230
+ To illustrate why, suppose you have 5 tests that call `TestData.uses_test_data`
1231
+ and 5 that call `TestData.uses_rails_fixtures`. If a test that calls
1232
+ `TestData.uses_test_data` is followed by another that calls `uses_test_data`,
1233
+ the only operation needed by the second call will be a rollback to the savepoint
1234
+ taken after the test data was loaded. If, however, a `uses_test_data` test is
1235
+ followed by a `uses_rails_fixtures` test, then a lot more work is required:
1236
+ first a rollback, then the truncation of the test data, then a load of the
1237
+ fixtures followed by creation of a new savepoint—which would in tunr be undone
1238
+ again if the _next_ test happened to call `uses_test_data`. Switching between
1239
+ tests that use different sources of test data can cause significant unnecessary
1240
+ thrashing.
1241
+
1242
+ To illustrate the above, if all of these tests ran in random order (the
1243
+ default), you might see:
1244
+
1245
+ ```
1246
+ $ bin/rails test test/example_test.rb
1247
+ Run options: --seed 63999
1248
+
1249
+ # Running:
1250
+
1251
+ test_data -- loading test_data SQL dump
1252
+ . fixtures -- truncating tables, loading Rails fixtures
1253
+ . fixtures -- rolling back to Rails fixtures
1254
+ . test_data -- rolling back to clean test_data
1255
+ . fixtures -- truncating tables, loading Rails fixtures
1256
+ . test_data -- rolling back to clean test_data
1257
+ . fixtures -- truncating tables, loading Rails fixtures
1258
+ . test_data -- rolling back to clean test_data
1259
+ . fixtures -- truncating tables, loading Rails fixtures
1260
+ . test_data -- rolling back to clean test_data
1261
+ .
1262
+
1263
+ Finished in 2.449957s, 4.0817 runs/s, 4.0817 assertions/s.
1264
+ 10 runs, 10 assertions, 0 failures, 0 errors, 0 skips
1265
+ ```
1266
+
1267
+ So, what can you do to speed this up? The most effective strategy to avoiding
1268
+ this churn is to group the execution of each tests that use each source of test
1269
+ data into sub-suites that are run serially, on e after the other.
1270
+
1271
+ * If you're using Rails' defualt Minitest, we wrote a gem called
1272
+ [minitest-suite](https://github.com/testdouble/minitest-suite) to accomplish
1273
+ exactly this. Just declare something like `suite :test_data` or `suite
1274
+ :fixtures` at the top of each test class
1275
+ * If you're using RSpec, the
1276
+ [tag](https://relishapp.com/rspec/rspec-core/v/3-10/docs/command-line/tag-option)
1277
+ feature can help you organize your tests by type, but you'll likely have to
1278
+ run a separate CLI invocation for each to avoid the tests from being
1279
+ interleaved
1280
+
1281
+ Here's what the same example would do at run-time after adding
1282
+ [minitest-suite](https://github.com/testdouble/minitest-suite):
1283
+
1284
+ ```
1285
+ $ bin/rails test test/example_test.rb
1286
+ Run options: --seed 50105
1287
+
1288
+ # Running:
1289
+
1290
+ test_data -- loading test_data SQL dump
1291
+ . test_data -- rolling back to clean test_data
1292
+ . test_data -- rolling back to clean test_data
1293
+ . test_data -- rolling back to clean test_data
1294
+ . test_data -- rolling back to clean test_data
1295
+ . fixtures -- truncating tables, loading Rails fixtures
1296
+ . fixtures -- rolling back to clean fixtures
1297
+ . fixtures -- rolling back to clean fixtures
1298
+ . fixtures -- rolling back to clean fixtures
1299
+ . fixtures -- rolling back to clean fixtures
1300
+ .
1301
+
1302
+ Finished in 2.377050s, 4.2069 runs/s, 4.2069 assertions/s.
1303
+ 10 runs, 10 assertions, 0 failures, 0 errors, 0 skips
1304
+ ```
1305
+
1306
+ By grouping the execution in this way, the most expensive operations will
1307
+ usually only be run once: at the beginning of the first test in each suite.
1308
+
1309
+ #### Expensive data manipulation
1310
+
1311
+ If you're doing anything repeatedly that's data-intensive in your test setup
1312
+ after calling one of the `TestData.uses_*` methods, that operation is being
1313
+ repeated once per test, which could be very slow. Instead, you might consider
1314
+ moving that behavior into a [lifecycle hook](#lifecycle-hooks).
1315
+
1316
+ Any code passed to a lifecycle hook will only be executed when data is
1317
+ _actually_ loaded or truncated and its effect will be included in the
1318
+ transaction savepoint that the `test_data` gem rolls back between tests.
1319
+ Seriously, appropriately moving data adjustments into these hooks can cut your
1320
+ test suite's runtime by an order of magnitude.
1321
+
1322
+ #### Redundant test setup tasks
1323
+
1324
+ One of the most likely sources of unnecessary slowness is redundant test
1325
+ cleanup. The speed gained from sandwiching every expensive operation between
1208
1326
  transaction savepoints can be profound… but can also easily be erased by a
1209
1327
  single before-each hook calling
1210
1328
  [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner) to
@@ -1213,32 +1331,6 @@ time to take stock of everything that's called between tests during setup &
1213
1331
  teardown to ensure multiple tools aren't attempting to clean up the state of the
1214
1332
  database and potentially interfering with one another.
1215
1333
 
1216
- A second opportunity for optimization is to group tests that use the same type
1217
- of test data together, either into separate suites or by preventing them from
1218
- being run in random order across said types. For example, suppose you have 10
1219
- tests that call `TestData.uses_test_data` and 10 that call
1220
- `TestData.uses_rails_fixtures`. If a test that calls `TestData.uses_test_data`
1221
- is followed by another that calls `uses_test_data`, the only operation needed by
1222
- the second call will be a rollback to the savepoint taken after the test data
1223
- was loaded. If, however, a `uses_test_data` test is followed by a
1224
- `uses_rails_fixtures` test, then the test data will be truncated and the
1225
- fixtures loaded and new savepoints created (which would then be undone again if
1226
- the _next_ test happened to call `uses_test_data`).
1227
-
1228
- As a result of the above, the marginal runtime cost for each `TestData.uses_*`
1229
- method depends on which kinds of test precedes and follows it. That means your
1230
- tests will run faster overall if the tests that call `TestData.uses_test_data`
1231
- are run as a group separately from your tests that rely on
1232
- `TestData.uses_clean_slate` or `TestData.uses_rails_fixtures`. Separating your
1233
- tests into logical groups pretty trivial if you're using RSpec, as the
1234
- [tag](https://relishapp.com/rspec/rspec-core/v/3-10/docs/command-line/tag-option)
1235
- feature was built with this sort of need in mind. If you're using Minitest, you
1236
- might consider organizing the tests in different directories and running
1237
- multiple commands to execute them (e.g. `bin/rails test test/test_data_tests`
1238
- and `bin/rails test/factory_tests`). Every CI configuration is different,
1239
- however, and you may find yourself needing to get creative in configuring things
1240
- to achieve the fastest build time.
1241
-
1242
1334
  ## Code of Conduct
1243
1335
 
1244
1336
  This project follows Test Double's [code of
data/example/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- test_data (0.2.1)
4
+ test_data (0.2.2)
5
5
  railties (~> 6.0)
6
6
 
7
7
  GEM
@@ -0,0 +1,82 @@
1
+ # Regression test to make sure we don't load fixtures too many times
2
+
3
+ class HookCounter
4
+ def self.count
5
+ @call_count || 0
6
+ end
7
+
8
+ def self.count!
9
+ @call_count ||= 0
10
+ @call_count += 1
11
+ end
12
+ end
13
+ at_exit do
14
+ if TestData.statistics.load_rails_fixtures_count > 2 # could be 1 if :all runs first, 2 if :boops only does
15
+ raise "Rails fixture load was called #{TestData.statistics.load_rails_fixtures_count} times, shouldn't be more than 2!"
16
+ end
17
+ if HookCounter.count > 2
18
+ raise "Rails fixture load hook was called #{HookCounter.count} times, shouldn't be more than 2!"
19
+ end
20
+ end
21
+
22
+ require "test_helper"
23
+
24
+ TestData.prevent_rails_fixtures_from_loading_automatically!
25
+
26
+ TestData.config do |config|
27
+ config.after_rails_fixture_load {
28
+ HookCounter.count!
29
+ }
30
+ end
31
+
32
+ class PartialFixtureTest < ActiveSupport::TestCase
33
+ fixtures :boops
34
+
35
+ setup do
36
+ TestData.uses_rails_fixtures(self)
37
+ end
38
+
39
+ def test_has_only_boops
40
+ assert boops(:boop_1)
41
+ assert_raises(NameError) { method(:pants) }
42
+ end
43
+ end
44
+
45
+ class AllFixtureTest < ActiveSupport::TestCase
46
+ fixtures :all
47
+
48
+ setup do
49
+ TestData.uses_rails_fixtures(self)
50
+ end
51
+
52
+ def test_has_both
53
+ assert boops(:boop_1)
54
+ assert pants(:pant_1)
55
+ end
56
+ end
57
+
58
+ class AllFixtureTest2 < ActiveSupport::TestCase
59
+ fixtures :all
60
+
61
+ setup do
62
+ TestData.uses_rails_fixtures(self)
63
+ end
64
+
65
+ def test_has_both
66
+ assert boops(:boop_1)
67
+ assert pants(:pant_1)
68
+ end
69
+ end
70
+
71
+ class AllFixtureTest3 < ActiveSupport::TestCase
72
+ fixtures :all
73
+
74
+ setup do
75
+ TestData.uses_rails_fixtures(self)
76
+ end
77
+
78
+ def test_has_both
79
+ assert boops(:boop_1)
80
+ assert pants(:pant_1)
81
+ end
82
+ end
@@ -21,19 +21,22 @@ module TestData
21
21
 
22
22
  def load_requested(test_instance:)
23
23
  ActiveRecord::FixtureSet.reset_cache
24
- test_instance.instance_variable_set(:@loaded_fixtures, @already_loaded_rails_fixtures[test_instance.class])
24
+ test_instance.instance_variable_set(:@loaded_fixtures,
25
+ @already_loaded_rails_fixtures.slice(*test_instance.class.fixture_table_names))
25
26
  test_instance.instance_variable_set(:@fixture_cache, {})
26
27
  end
27
28
 
28
29
  def loaded?(test_instance:)
29
- @already_loaded_rails_fixtures[test_instance.class].present?
30
+ test_instance.class.fixture_table_names.all? { |table_name|
31
+ @already_loaded_rails_fixtures.key?(table_name)
32
+ }
30
33
  end
31
34
 
32
35
  def load(test_instance:)
33
36
  test_instance.pre_loaded_fixtures = false
34
37
  test_instance.use_transactional_tests = false
35
38
  test_instance.__test_data_gem_setup_fixtures
36
- @already_loaded_rails_fixtures[test_instance.class] = test_instance.instance_variable_get(:@loaded_fixtures)
39
+ @already_loaded_rails_fixtures = test_instance.instance_variable_get(:@loaded_fixtures)
37
40
  @statistics.count_load_rails_fixtures!
38
41
  @config.after_rails_fixture_load_hook.call
39
42
  end
@@ -1,3 +1,3 @@
1
1
  module TestData
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
data/script/test CHANGED
@@ -52,6 +52,7 @@ bin/rails test test/integration/transaction_committing_boops_test.rb
52
52
  # Run a test that prevents Rails fixtures for preloading and then loads them in a transaction
53
53
  bin/rails test test/integration/rails_fixtures_override_test.rb
54
54
  bundle exec rspec spec/requests/rails_fixtures_override_spec.rb
55
+ bin/rails test test/integration/fixture_load_count_test.rb
55
56
 
56
57
  # Run a test that forgets to prevent Rails fixtures but then tries to load them in a transaction
57
58
  bin/rails test test/integration/rails_fixtures_double_load_test.rb
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test_data
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-30 00:00:00.000000000 Z
11
+ date: 2021-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -103,6 +103,7 @@ files:
103
103
  - example/test/integration/better_mode_switching_demo_test.rb
104
104
  - example/test/integration/boops_that_boop_boops_test.rb
105
105
  - example/test/integration/dont_dump_tables_test.rb
106
+ - example/test/integration/fixture_load_count_test.rb
106
107
  - example/test/integration/load_rollback_truncate_test.rb
107
108
  - example/test/integration/migrated_boops_test.rb
108
109
  - example/test/integration/mode_switching_demo_test.rb