Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Your Test Suite is Making Too Many Database Calls!

Your Test Suite is Making Too Many Database Calls!

Originally given at RailsConf 2022. Recording at https://www.youtube.com/watch?v=LOlG4kqfwcg

On a recent project, I sped up a test suite 15% by making a change to a single factory. This suite, like many others (including yours!), was making way too many database calls. It’s so easy to accidentally add extra queries to factories and test setup and these can compound to shockingly large numbers.

The chaos is your opportunity! Learn to profile and fix hot spots, build big-picture understanding through diagrams, and write code that is resistant to extraneous queries. This talk will equip you to take back control of your build times and maybe impress your teammates in the process.

Joël Quenneville

May 23, 2022
Tweet

More Decks by Joël Quenneville

Other Decks in Programming

Transcript

  1. # BAD it "#full_name concatenates first and last name" do

    user = create( :user, first_name: "Joël", last_name: "Quenneville" ) expect(user.full_name).to eq "Joël Quenneville" end
  2. # GOOD it "#full_name concatenates first and last name" do

    user = User.new( first_name: "Joël", last_name: "Quenneville" ) expect(user.full_name).to eq "Joël Quenneville" end
  3. let(:user1) { create(:user) } let(:user2) { create(:user) } let(:organization) {

    create(:organization), users: [user1, user2] } # this test only needs an org it "does something" # this test needs and org + some users it "does something whith multiple users"
  4. let(:contact) { create(:contact) } let(:user) { create(:user, admin: true, contacts:

    [contact]) } # NEEDS 1 query # MAKES 3 queries (2x CREATE + 1x UPDATE) it "does something for a regular user" do user.update(admin: false) ... end # NEEDS 2 queries # MAKES 2 queries it "does something for an admin with contact info"
  5. # this test only needs an org it "does something"

    do org = create(:organization) ... end # this test needs and org + some users it "does something whith multiple users" do user1 = create(:user) user2 = create(:user) org = create(:organization, users: [user1, user2] ... end
  6. it "does something" do # this makes 4 INSERT queries

    org = create(:organization) expect(org.do_something).to eq "something" end
  7. factory :organization do members { create_list(:user, 3 } sequence(:name) {

    |n| "Organization #{n}" } end factory :user do association :organization end
  8. it "does something given a user" do user = create(:user)

    service = MyService.new(user) expect(service.call()).to eq "something" end
  9. factory :organization do sequence(:name) { |n| "Organization #{n}" } trait

    :with_users do members { create_list(:user, 3 } end end
  10. [TEST PROF INFO] Factories usage Total: 15285 Total top-level: 10286

    Total time: 04:31.222 (out of 07.16.124) Total uniq factories: 119 total top-level total time time per call top-level time name 6091 2715 115.7671s 0.0426s 50.2517s user 2142 2098 93.3152s 0.0444s 92.1915s post ...
  11. class Organization < ApplicationRecord before_create :generate_admin def generate_admin admin =

    User.create!( email: "admin@#{organization.domain}", admin: true ) self.admin = admin end end
  12. it "does something" do admin = create(:user, admin: true) org

    = create(:organization, admin: admin) # ... end
  13. it "does something" do admin = create(:user, admin: true) org

    = create(:organization) # CODE SMELL! # This now makes 3 INSERTs + 1 UPDATE calls org.update(admin: admin) # ... end
  14. factory :organization_with_events do staff { create_list(:staff, 1) } venues {

    create_list(:venue, 1) after(:build) do |org| create_list(:event, 3, venue: org.venues.first) end end
  15. factory :organization_with_events do staff { create_list(:staff, 1) } venues {

    create_list(:venue, 1) after(:build) do |org| create_list(:event, 3, venue: org.venues.first, staff: org.staff.first) end end