Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Testing 201, or: Great Expectations

Testing 201, or: Great Expectations

Ruby developers are a testy bunch. Who else writes more tests that we do? Which makes sense! Our language allows us freedom, and the opportunity to abuse it.

Most of us learned to test informally, from existing projects or tutorials. Sadly, that often leads us to the false conclusion that is all about verifying that our code works. Would you believe that’s only the third most important reason to test?

Let’s take a non-dogmatic look at the less obvious roles for testing with some real examples. We can get so much more from testing.

Joseph Mastey

August 04, 2020
Tweet

More Decks by Joseph Mastey

Other Decks in Programming

Transcript

  1. class CreateShipmentHistoryReport attr_accessor :user, :shipments def initialize(user) @user = user

    @shipments = user.shipments end def process shipments.sort_by(&:created_at) data = shipments.map do |shipment| shipment.to_json.values + [ shipment.user.name, shipment.product.name ] end file = CSV.open(tmp_filename) do |csv| csv << shipments.first.to_json.keys + ['user', 'product'] data.map do |row| csv << row end end persist_csv_to_s3(file) notify_user(user.email) file end end
  2. def process shipments.sort_by(&:created_at) data = shipments.map do |shipment| shipment.to_json.values +

    [ shipment.user.name, shipment.product.name ] end # continued below...
  3. # ... process continued file = CSV.open(tmp_filename) do |csv| csv

    << shipments.first.to_json.keys + ['user', 'product'] data.map do |row| csv << row end end persist_csv_to_s3(file) notify_user(user.email) file end
  4. class CreateShipmentHistoryReport attr_accessor :user, :shipments def initialize(user) @user = user

    @shipments = user.shipments end def process shipments.order('created_at desc') data = shipments.map do |shipment| shipment.to_json.values + [ shipment.user.name, shipment.product.name ] end file = CSV.open(tmp_filename) do |csv| csv << shipments.first.to_json.keys + ['user', 'product'] data.map do |row| csv << row end end persist_csv_to_s3(file) notify_user(user.email) file end end
  5. describe CreateShipmentHistoryReport do describe "#process" do # ... it "generates

    an array of strings" do expect(csv_data.length).to eq(shipments.length) expect(csv_data).to all(be_an(Array)) end it "matches the expected output" do expect(csv_data).to eq(output_records) end end end
  6. let(:user) { create(:user, :signup_complete) } let(:subscription) { create(:subscription) } before

    do user.subscriptions << subscription expect(user) .to receive(:shipments) .and_return(shipments) end
  7. let!(:shipment_1) { create(:shipment, product: product_1, created_at: 2.days.ago) } let!(:shipment_2) {

    create(:shipment, product: product_2, created_at: 3.days.ago) } let!(:shipment_3) { create(:shipment, product: product_3, created_at: 4.days.ago) } let!(:shipment_4) { create(:shipment, product: product_4, created_at: 5.days.ago) }
  8. let(:output_records) do [ [shipment_4.created_on, shipment_4.completed_on, user.name, product_4.name], [shipment_3.created_on, shipment_3.completed_on, user.name,

    product_3.name], [shipment_2.created_on, shipment_2.completed_on, user.name, product_2.name], [shipment_1.created_on, shipment_1.completed_on, user.name, product_1.name], ] end
  9. describe CreateShipmentHistoryReport do describe "#process" do let(:user) { create(:user, :signup_complete)

    } let(:subscription) { create(:subscription) } let(:product_1) { create(:product) } let(:product_2) { create(:product) } let(:product_3) { create(:product) } let(:product_4) { create(:product) } let!(:shipment_1) { create(:shipment, product: product_1, created_at: 2.days.ago) } let!(:shipment_2) { create(:shipment, product: product_2, created_at: 3.days.ago) } let!(:shipment_3) { create(:shipment, product: product_3, created_at: 4.days.ago) } let!(:shipment_4) { create(:shipment, product: product_4, created_at: 5.days.ago) } let(:subject) { described_class.new(user).process } let(:csv_data) { CSV.parse(subject) } let(:shipments) do [ shipment_1, shipment_2, shipment_3, shipment_4 ] end let(:output_records) do [ [shipment_4.created_on, shipment_4.completed_on, user.name, product_4.name], [shipment_3.created_on, shipment_3.completed_on, user.name, product_3.name], [shipment_2.created_on, shipment_2.completed_on, user.name, product_2.name], [shipment_1.created_on, shipment_1.completed_on, user.name, product_1.name], ] end before do user.subscriptions << subscription expect(user) .to receive(:shipments) .and_return(shipments) expect_any_instance_of(described_class) .to receive(:save_to_s3) .and_return(true) expect_any_instance_of(described_class) .to receive(:email_user) .and_return(true) end it "generates an array of strings" do expect(csv_data.length).to eq(shipments.length) expect(csv_data).to all(be_an(Array)) end it "matches the expected output" do expect(csv_data).to eq(output_records) end end end
  10. let(:csv_data) { CSV.parse(subject) } before do expect_any_instance_of(described_class) .to receive(:save_to_s3) .and_return(true)

    expect_any_instance_of(described_class) .to receive(:email_user) .and_return(true) end
  11. def data(shipments: @shipments) shipments.sort_by!(&:created_at) shipments.map do |shipment| shipment.to_json.values + [

    shipment.user.name, shipment.product.name ] end end def headers shipments.first.to_json.keys + ['user', 'product'] end
  12. before do # expect_any_instance_of(described_class) # .to receive(:save_to_s3) # .and_return(true) #

    expect_any_instance_of(described_class) # .to receive(:email_user) # .and_return(true) end
  13. def data(shipments) shipments.sort_by!(&:created_at) shipments.map do |shipment| shipment.to_json.values + [ shipment.user.name,

    shipment.product.name ] end end def headers shipments.first.to_json.keys + ['user', 'product'] end
  14. describe "#serialize" do it "serializes some shipment and user data"

    do shipment = create(:shipment) report = described_class.new(user, []) result = report.serialize(shipment) expect(result).to eq({ created_on: shipment.created_on, completed_on: shipment.completed_on, user: shipment.user.name, product: shipment.product.name, }) end end
  15. # let(:output_records) do # [ # [shipment_4.created_on, shipment_4.completed_on, # user.name,

    product_4.name], # [shipment_3.created_on, shipment_3.completed_on, # user.name, product_3.name], # [shipment_2.created_on, shipment_2.completed_on, # user.name, product_2.name], # [shipment_1.created_on, shipment_1.completed_on, # user.name, product_1.name], # ] # end
  16. describe "ordering" do it "reorders shipments by their creation date"

    do report = described_class.new(user,[shipment_1, shipment_2]) result = report.data expect(result.map(&:first)).to eq( [shipment_2.created_on, shipment_1.created_on] ) end end
  17. Factory AR records AR queries create(:user) 1 10 create(:meal) 5

    41 create(:full_menu) 94 584 create(:weekly_basket) 104 644 create(:user, :with_order_history) 379 2336
  18. describe "ordering" do it "reorders shipments by their creation date"

    do report = described_class.new(user, [shipment_1, shipment_2]) result = report.data expect(result.map(&:first)).to eq( [shipment_2.created_on, shipment_1.created_on] ) end end
  19. describe "ordering" do it "reorders shipments by their creation date"

    do old = create_shipment(date: 9.days.ago) new = create_shipment(date: 2.days.ago) report = described_class.new(user, [new, old]) result = report.data expect(result.map(&:first)).to eq( [old.created_on, new.created_on] ) end end
  20. it "serializes users as passed into the service" do users

    = create_list(:user, 3) response = subject.new(User.all).process response = response[0][4] expect(response).to eq(users.first.name) end
  21. it "serializes users as passed into the service" do users

    = create_list(:user, 3) serialized_users = subject.new.serialize(users) response = serialized_users.first.full_name expect(response).to eq(users.first.name) end
  22. let!(:shipment_1) { create(:shipment, product: product_1, created_at: 2.days.ago) } let!(:shipment_2) {

    create(:shipment, product: product_2, created_at: 3.days.ago) } let!(:shipment_3) { create(:shipment, product: product_3, created_at: 4.days.ago) } let!(:shipment_4) { create(:shipment, product: product_4, created_at: 5.days.ago) }
  23. let(:output_records) do [ [shipment_4.created_on, shipment_4.completed_on, user.name, product_4.name], [shipment_3.created_on, shipment_3.completed_on, user.name,

    product_3.name], [shipment_2.created_on, shipment_2.completed_on, user.name, product_2.name], [shipment_1.created_on, shipment_1.completed_on, user.name, product_1.name], ] end
  24. describe "ordering" do it "reorders shipments by their creation date"

    do old = create_shipment(date: 9.days.ago) new = create_shipment(date: 2.days.ago) report = described_class.new(user, [new, old]) result = report.data expect(result.map(&:first)).to eq( [old.created_on, new.created_on] ) end end
  25. # let(:product_1) { create(:product) } # let(:product_2) { create(:product) }

    # let(:product_3) { create(:product) } # let(:product_4) { create(:product) } # let!(:shipment_1) { create(:shipment, product: product_1, # created_at: 2.days.ago) } # let!(:shipment_2) { create(:shipment, product: product_2, # created_at: 3.days.ago) } # let!(:shipment_3) { create(:shipment, product: product_3, # created_at: 4.days.ago) } # let!(:shipment_4) { create(:shipment, product: product_4, # created_at: 5.days.ago) } # # let(:shipments) do # [ # shipment_1, # shipment_2, # shipment_3, # shipment_4 # ] # end # let(:output_records) do # [ # [shipment_4.created_on, shipment_4.completed_on, # user.name, product_4.name], # [shipment_3.created_on, shipment_3.completed_on, # user.name, product_3.name], # [shipment_2.created_on, shipment_2.completed_on, # user.name, product_2.name], # [shipment_1.created_on, shipment_1.completed_on, # user.name, product_1.name], # ] # end
  26. describe "ordering" do it "reorders shipments by their creation date"

    do old = create_shipment(date: 9.days.ago) new = create_shipment(date: 2.days.ago) report = described_class.new(user, [new, old]) result = report.data expect(result.map(&:first)).to eq( [old.created_on, new.created_on] ) end end
  27. describe "ordering" do it "reorders shipments by their creation date"

    do # Given old = create_shipment(date: 9.days.ago) new = create_shipment(date: 2.days.ago) # When report = described_class.new(user, [new, old]) result = report.data # Then expect(result.map(&:first)).to eq( [old.created_on, new.created_on] ) end end
  28. it "reorders shipments by their creation date" do old =

    create_shipment(date: 9.days.ago) new = create_shipment(date: 2.days.ago) report = described_class.new(user, [new, old]) expect(report.data.map(&:first)).to eq([old, new]) end
  29. it "returns order units summed, divided by units per shipper"

    do create_store_order(count_units: 4) create_store_order(count_units: 8) subject = described_class.new(store_orders: store_orders) total_shippers = subject.total_shipper_count expect(total_shippers).to eq(3) end
  30. it "returns a ceiling rounded value" do create_store_order(count_units: 7) subject

    = described_class.new(store_orders: store_orders) total_shippers = subject.total_shipper_count expect(total_shippers).to eq(2) end
  31. let!(:shipment_1) { create(:shipment, product: product_1, created_at: 2.days.ago) } let!(:shipment_2) {

    create(:shipment, product: product_2, created_at: 3.days.ago) } let!(:shipment_3) { create(:shipment, product: product_3, created_at: 4.days.ago) } let!(:shipment_4) { create(:shipment, product: product_4, created_at: 5.days.ago) }
  32. it "returns order units summed, divided by units per shipper"

    do create_store_order(count_units: 4) create_store_order(count_units: 8) subject = described_class.new(store_orders: store_orders) total_shippers = subject.total_shipper_count expect(total_shippers).to eq(3) end it "returns a ceiling rounded value" do create_store_order(count_units: 7) subject = described_class.new(store_orders: store_orders) total_shippers = subject.total_shipper_count expect(total_shippers).to eq(2) end
  33. describe "#total_shipper_count" do subject { described_class.new(store_orders: StoreOrder.all) } context "even

    division" do let!(:order1) { create_store_order(count_units: 4) } let!(:order2) { create_store_order(count_units: 8) } let!(:expected_total) { 3 } # ... lots of other tests ... it "returns order units summed, divided by units per shipper" do expect(subject.total_shipper_count).to eq(expected_total) end end end
  34. context "rounding" do let!(:order) { create_store_order(count_units: 7) } let!(:expected_total) {

    2 } # ... lots of other tests ... it "returns order units summed, divided by units per shipper" do expect(subject.total_shipper_count).to eq(expected_total) end end
  35. it "returns a user summary for printing" do user =

    User.new(id: 5, full_name: "Dave G", role: :admin) expected = "(#{user.id}) #{user.full_name}, #{user.role}" summary = user_summary(user) expect(summary).to eq(expected) end
  36. it "returns a user summary for printing" do user =

    User.new(id: 5, full_name: "Dave G", role: :admin) summary = user_summary(user) expect(summary).to eq("(5) Dave G, admin") end
  37. describe "#process" do it “has a correct return value" end

    describe "#process" do it "returns the number of records correctly persisted" end
  38. it “picks randomly, but correctly” do double1 = double double2

    = double result = [double1, double2].sample expect(result).to eq(double1) end # expected: #<Double (anonymous)> # got: #<Double (anonymous)>
  39. it “picks randomly, but correctly” do double1 = double("first record")

    double2 = double("second record”) result = [double1, double2].sample expect(result).to eq(double1) end # expected: #<Double "first record"> # got: #<Double "second record">
  40. it "checks result size" do arr = [1,2,3] response =

    arr.length expect(response > 3).to be_true end # expected true # got false
  41. it "checks result size" do arr = [1,2,3] response =

    arr.length expect(response).to be > 3 end # expected: > 3 # got: 3
  42. it "checks validity of a record" do thing = double(valid?:

    true) expect(thing.valid?).to be_false end # expected: false # got: true
  43. it "checks validity of a record" do thing = double("order",

    valid?: true) expect(thing).not_to be_valid end # expected `#<Double "order">.valid?` to return false, got true
  44. class JobLog < ActiveRecord::Base validate :failure_message, presence: true, if: :failed?

    end it "requires failure_message for failures” do job = JobLog.new(status: :failed) expect(job).to validate_presence_of(:failure_message) job = JobLog.new(status: :completed) expect(job).not_to validate_presence_of(:failure_message) end
  45. def user_summary(user) "(#{user.id}) #{user.full_name}, #{user.role}" end it "returns a user

    summary for printing, bad" do user = double(id: 5, full_name: "Dave G", role: :admin) response = user_summary(user) expect(response).to eq("(5) Dave G, admin") end
  46. it "returns a user summary for printing, better" do user

    = instance_double(User, id: 5, full_name: "Dave G", role: :admin) response = user_summary(user) expect(response).to eq("(5) Dave G, admin") end
  47. it "returns a user summary for printing, better" do user

    = User.new(id: 5, full_name: "Dave G", role: :admin) expect(user_summary(user)).to eq("(5) Dave G, admin") end