Slide 1

Slide 1 text

WHAT FACTORIES ARE HIDING FROM YOU ERIC ROBERTS — @eroberts

Slide 2

Slide 2 text

WHAT IS A FACTORY?

Slide 3

Slide 3 text

Test factories are helpers for creating data that can be used in tests.

Slide 4

Slide 4 text

FactoryGirl.define do factory :user do name "Eric Roberts" email "eric@boltmade.com" end end user = FactoryGirl.create(:user) #=> # user.name #=> "Eric Roberts" user.email #=> "eric@boltmade.com"

Slide 5

Slide 5 text

SO, WHAT'S WRONG WITH FACTORIES?

Slide 6

Slide 6 text

THEY'RE SLOW!

Slide 7

Slide 7 text

FactoryGirl.define do factory :user do name "Eric Roberts" email "eric@boltmade.com" end end

Slide 8

Slide 8 text

FactoryGirl.create(:user)

Slide 9

Slide 9 text

INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [ ["name", "Eric"], ["email", "eric@boltmade.com"], ["created_at", "2015-04-21 01:08:49.409179"], ["updated_at", "2015-04-21 01:08:49.409179"] ]

Slide 10

Slide 10 text

FactoryGirl.define do factory :user do association :company name "Eric Roberts" email "eric@boltmade.com" end factory :company do name "Boltmade" end end

Slide 11

Slide 11 text

FactoryGirl.create(:user)

Slide 12

Slide 12 text

INSERT INTO "companies" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [ ["name", "Boltmade"], ["created_at", "2015-04-21 01:22:24.307976"], ["updated_at", "2015-04-21 01:22:24.307976"] ] INSERT INTO "users" ("name", "email", "company_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [ ["name", "Eric"], ["email", "eric@boltmade.com"], ["company_id", 1], ["created_at", "2015-04-21 01:22:24.311557"], ["updated_at", "2015-04-21 01:22:24.311557"] ]

Slide 13

Slide 13 text

SAVING 0.1 SECONDS PER TEST ON 1200 TESTS SAVES YOU A TOTAL OF 2 MINUTES EVERY TIME THE TESTS RUN.

Slide 14

Slide 14 text

DON'T YOU KNOW ABOUT build_stubbed?

Slide 15

Slide 15 text

WHAT ELSE IS WRONG WITH THEM?

Slide 16

Slide 16 text

THEY MAKE BAD DESIGN EASY

Slide 17

Slide 17 text

WHAT IS THE POINT OF TDD?

Slide 18

Slide 18 text

FEWER BUGS

Slide 19

Slide 19 text

SELF-TESTING CODE

Slide 20

Slide 20 text

REFACTOR WITH CONFIDENCE

Slide 21

Slide 21 text

BETTER DESIGN

Slide 22

Slide 22 text

LET'S TAKE A LOOK AT SOME CODE

Slide 23

Slide 23 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.inject([0,0]) do |(min, max), customer| min += customer.revenue * customer.rate.min max += customer.revenue * customer.rate.max [min, max] end end end class Customer < ActiveRecord::Base belongs_to :rate belongs_to :estimator # revenue method added by ActiveRecord end class Rate < ActiveRecord::Base def min self[:min] / 100.to_f end def max self[:max] / 100.to_f end end

Slide 24

Slide 24 text

FactoryGirl.define do factory :estimator do end factory :customer do association :rate association :estimator revenue 100 end factory :rate do min 80 max 90 end end

Slide 25

Slide 25 text

RSpec.describe Estimator do subject { customer.estimator } let(:customer) { create :customer } describe "#projection" do it "should return the sum of the estimated min and max projections" do expect(subject.projection).to eq [ customer.revenue * customer.rate.min, customer.revenue * customer.rate.max ] end end end

Slide 26

Slide 26 text

. Finished in 0.0482 seconds (files took 0.38182 seconds to load) 1 example, 0 failures

Slide 27

Slide 27 text

REQUIREMENT: DON'T USE FACTORIES.

Slide 28

Slide 28 text

RSpec.describe Estimator do subject { customer.estimator } let(:customer) { create :customer } describe "#projection" do it "should return the sum of the estimated min and max projections" do expect(subject.projection).to eq [ customer.revenue * customer.rate.min, customer.revenue * customer.rate.max ] end end end

Slide 29

Slide 29 text

RSpec.describe Estimator do subject { Estimator.new } describe "#projection" do let(:customer) { double } before do allow(subject).to receive(:customers).and_return([customer]) end [...] end end

Slide 30

Slide 30 text

F Failures: 1) Estimator#projection should return the sum of the estimated min and max projections Failure/Error: expect(subject.projection).to eq [ Double received unexpected message :revenue with (no args) # ./lib/estimator.rb:8:in `block in projection' # ./lib/estimator.rb:7:in `each' # ./lib/estimator.rb:7:in `inject' # ./lib/estimator.rb:7:in `projection' # ./spec/estimator_spec.rb:17:in `block (3 levels) in ' Finished in 0.01076 seconds (files took 0.38914 seconds to load) 1 example, 1 failure

Slide 31

Slide 31 text

RSpec.describe Estimator do subject { Estimator.new } describe "#projection" do let(:customer) { double } before do allow(subject).to receive(:customers).and_return([customer]) allow(customer).to receive(:revenue).and_return(100) end [...] end end

Slide 32

Slide 32 text

RSpec.describe Estimator do subject { Estimator.new } describe "#projection" do let(:customer) { double } let(:rate) { double } before do allow(subject).to receive(:customers).and_return([customer]) allow(customer).to receive(:revenue).and_return(100) allow(customer).to receive(:rate).and_return(rate) end [...] end end

Slide 33

Slide 33 text

RSpec.describe Estimator do subject { Estimator.new } describe "#projection" do let(:customer) { double } let(:rate) { double } before do allow(subject).to receive(:customers).and_return([customer]) allow(customer).to receive(:revenue).and_return(100) allow(customer).to receive(:rate).and_return(rate) allow(rate).to receive(:min).and_return(80) allow(rate).to receive(:max).and_return(90) end [...] end end

Slide 34

Slide 34 text

RSpec.describe Estimator do subject { Estimator.new } describe "#projection" do let(:customer) { double } let(:rate) { double } before do allow(subject).to receive(:customers).and_return([customer]) allow(customer).to receive(:revenue).and_return(100) allow(customer).to receive(:rate).and_return(rate) allow(rate).to receive(:min).and_return(80) allow(rate).to receive(:max).and_return(90) end it "should return the sum of the estimated min and max projections" do expect(subject.projection).to eq [ customer.revenue * customer.rate.min, customer.revenue * customer.rate.max ] end end end

Slide 35

Slide 35 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.inject([0,0]) do |(min, max), customer| min += customer.revenue * customer.rate.min max += customer.revenue * customer.rate.max [min, max] end end end

Slide 36

Slide 36 text

. Finished in 0.0171 seconds (files took 0.40143 seconds to load) 1 example, 0 failures

Slide 37

Slide 37 text

THAT'S 3x FASTER!

Slide 38

Slide 38 text

TESTING WITH ALL THESE STUBS IS PAINFUL

Slide 39

Slide 39 text

THAT'S GOOD.

Slide 40

Slide 40 text

To paraphrase Justin Searls in The Failures of "Intro to TDD": Your tools and practices should encourage you to do the right thing at each step in your workflow.

Slide 41

Slide 41 text

RSpec.describe Estimator do subject { Estimator.new } describe "#projection" do let(:customer) { double } let(:rate) { double } before do allow(subject).to receive(:customers).and_return([customer]) allow(customer).to receive(:revenue).and_return(100) allow(customer).to receive(:rate).and_return(rate) allow(rate).to receive(:min).and_return(80) allow(rate).to receive(:max).and_return(90) end it "should return the sum of the estimated min and max projections" do expect(subject.projection).to eq [ customer.revenue * customer.rate.min, customer.revenue * customer.rate.max ] end end end

Slide 42

Slide 42 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.inject([0,0]) do |(min, max), customer| min += customer.revenue * customer.rate.min max += customer.revenue * customer.rate.max [min, max] end end end

Slide 43

Slide 43 text

ESTIMATOR KNOWS WAY TOO MUCH ABOUT CUSTOMER

Slide 44

Slide 44 text

WHAT DO WE WANT FROM CUSTOMER?

Slide 45

Slide 45 text

#PROJECTION

Slide 46

Slide 46 text

RSpec.describe Customer do subject { Customer.new(revenue: 100) } let(:rate) { double } let(:min) { 80 } let(:max) { 90 } before do allow(subject).to receive(:rate).and_return(rate) allow(rate).to receive(:min).and_return(min) allow(rate).to receive(:max).and_return(max) end it "should return the min and max projection" do expect(subject.projection).to eq [ subject.revenue * rate.min, subject.revenue * rate.max ] end end

Slide 47

Slide 47 text

class Customer < ActiveRecord::Base belongs_to :rate belongs_to :estimator def projection [ revenue * rate.min, revenue * rate.max ] end end

Slide 48

Slide 48 text

LET'S REVISIT THE ESTIMATOR TESTS

Slide 49

Slide 49 text

RSpec.describe Estimator do subject { Estimator.new } describe "#projection" do let(:customer) { double } let(:rate) { double } before do allow(subject).to receive(:customers).and_return([customer]) allow(customer).to receive(:revenue).and_return(100) allow(customer).to receive(:rate).and_return(rate) allow(rate).to receive(:min).and_return(80) allow(rate).to receive(:max).and_return(90) end it "should return the sum of the estimated min and max projections" do expect(subject.projection).to eq [ customer.revenue * customer.rate.min, customer.revenue * customer.rate.max ] end end end

Slide 50

Slide 50 text

RSpec.describe Estimator do subject { Estimator.new } describe "#projection" do let(:customer) { double } let(:rate) { double } let(:projection) { [80,90] } before do allow(subject).to receive(:customers).and_return([customer]) allow(customer).to receive(:projection).and_return(projection) end it "should return the sum of the estimated min and max projections" do expect(subject.projection).to eq [ projection.min, projection.max ] end end end

Slide 51

Slide 51 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.inject([0,0]) do |(min, max), customer| min += customer.projection.min max += customer.projection.min [min, max] end end end

Slide 52

Slide 52 text

.. Finished in 0.01977 seconds (files took 0.39439 seconds to load) 2 examples, 0 failures

Slide 53

Slide 53 text

RSpec.describe Customer do subject { Customer.new } describe "#projection" do let(:rate) { double } before do allow(subject).to receive(:rate).and_return(rate) allow(rate).to receive(:min).and_return(min) allow(rate).to receive(:max).and_return(max) end it "should return the min and max projection" do expect(subject.projection).to eq [ customer.revenue * rate.min, customer.revenue * rate.max ] end end end

Slide 54

Slide 54 text

CUSTOMER KNOWS TOO MUCH ABOUT RATE

Slide 55

Slide 55 text

WHAT DO WE WANT TO DO WITH RATE?

Slide 56

Slide 56 text

[ rate.min * revenue, rate.max * revenue ] #=> [80, 90]

Slide 57

Slide 57 text

rate * revenue #=> [80, 90]

Slide 58

Slide 58 text

RSpec.describe Rate do subject { Rate.new(min: min, max: max) } let(:min) { 80 } let(:max) { 90 } describe "#*" do let(:multiplier) { 100 } it "should return the two possible rates" do expect(subject * multiplier).to eq [ min * multiplier, max * multiplier ] end end end

Slide 59

Slide 59 text

class Rate < ActiveRecord::Base def min self[:min] / 100.to_f end def max self[:max] / 100.to_f end def * other [ min * other, max * other ] end end

Slide 60

Slide 60 text

RSpec.describe Customer do subject { Customer.new(revenue: 100) } let(:rate) { double } let(:min) { 80 } let(:max) { 90 } before do allow(subject).to receive(:rate).and_return(rate) allow(rate).to receive(:min).and_return(min) allow(rate).to receive(:max).and_return(max) end it "should return the min and max projection" do expect(subject.projection).to eq [ subject.revenue * rate.min, subject.revenue * rate.max ] end end

Slide 61

Slide 61 text

RSpec.describe Customer do subject { Customer.new(revenue: revenue) } let(:revenue) { 100 } describe "#projection" do let(:rate) { double } before do allow(subject).to receive(:rate).and_return(rate) end it "should send the * message to rate" do expect(rate).to receive(:*).with(revenue) subject.projection end end end

Slide 62

Slide 62 text

class Customer < ActiveRecord::Base belongs_to :rate belongs_to :estimator def projection rate * revenue end end

Slide 63

Slide 63 text

LET'S REVISIT ESTIMATOR

Slide 64

Slide 64 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.inject([0,0]) do |(min, max), customer| min += customer.projection[0] max += customer.projection[1] [min, max] end end end

Slide 65

Slide 65 text

ESTIMATOR KNOWS TOO MUCH ABOUT RATE

Slide 66

Slide 66 text

WHAT I'D LIKE TO DO...

Slide 67

Slide 67 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.inject([0,0]) do |sum, customer| sum + customer.projection end end end

Slide 68

Slide 68 text

RSpec.describe MinMax do subject { MinMax.new(min, max) } let(:min) { 80 } let(:max) { 90 } describe "#+" do let(:other) { [min, max] } it "should return a new object that responds to min and max" do new_min_max = subject + other expect(new_min_max.min).to eq subject.min + other.min expect(new_min_max.max).to eq subject.max + other.max end end end

Slide 69

Slide 69 text

class MinMax attr_reader :min, :max def initialize(min, max) @min, @max = min, max end def + other self.class.new(min + other.min, max + other.max) end def self.zero new(0, 0) end end

Slide 70

Slide 70 text

MinMax.new(1, 10) #=> # MinMax.new(2, 10) #=> # MinMax.new(1, 10) + MinMax.new(2, 10) #=> #

Slide 71

Slide 71 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.inject(MinMax.zero) do |minmax, customer| minmax + customer.projection end end end

Slide 72

Slide 72 text

.... Finished in 0.0188 seconds (files took 0.40418 seconds to load) 4 examples, 0 failures

Slide 73

Slide 73 text

THE FINAL CODE

Slide 74

Slide 74 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.inject(MinMax.zero) do |minmax, customer| minmax + customer.projection end end end

Slide 75

Slide 75 text

class Estimator < ActiveRecord::Base has_many :customers def projection customers.map(&:projection).inject(MinMax.zero, :+) end end

Slide 76

Slide 76 text

class Customer < ActiveRecord::Base belongs_to :rate belongs_to :estimator def projection rate * revenue end end

Slide 77

Slide 77 text

class Rate < ActiveRecord::Base def min self[:min] / 100.to_f end def max self[:max] / 100.to_f end def * other [ min * other, max * other ] end end

Slide 78

Slide 78 text

class MinMax attr_reader :min, :max def initialize(min, max) @min, @max = min, max end def + other new_min = min + other.min new_max = max + other.max self.class.new(new_min, new_max) end def self.zero new(0, 0) end end

Slide 79

Slide 79 text

BEFORE

Slide 80

Slide 80 text

35.4: flog total 5.9: flog/method average 20.7: Estimator#projection lib/estimator.rb:6 4.1: Rate#min lib/rate.rb:4

Slide 81

Slide 81 text

AFTER

Slide 82

Slide 82 text

46.1: flog total 3.8: flog/method average 9.2: MinMax#+ lib/min_max.rb:8 7.5: Estimator#projection lib/estimator.rb:7 4.8: Rate#* lib/rate.rb:12 4.4: main#none 4.1: Rate#min lib/rate.rb:4

Slide 83

Slide 83 text

SO REMEMBER...

Slide 84

Slide 84 text

FACTORIES ARE BAD

Slide 85

Slide 85 text

USE ONLY ORGANIC, LOCALLY SOURCED DATA INSTEAD

Slide 86

Slide 86 text

That's all, folks! ERIC ROBERTS — @eroberts