Testing at Greenhouse – Can we Mock reliably ?



Testing at Greenhouse – Can we Mock reliably ?

0 0


gh_testing


On Github davidhan527 / gh_testing

Testing at Greenhouse

Magic Tricks of Testing

* 80% of this presentation is shamelessly stolen from points in this video. I spent most of my time here at greenhouse, learning and thinking about how I can take these priniciples and apply them to Greenhouse and that's what I want to share with everyone today

Objectives

part 1

- Maximizing coverage with the fewest tests possible

- Testing everything once

- Testing the interface and not the implementation

part 2

- How to mock reliably

* Basic Idea: Testing the interface as in, testing the incoming message and the side effect. Rather than testing the implementation details which are constantly changing. * Maximizing coverage with the fewest tests possible

Bad tests are:

Slow

Fragile

Expensive

* Most people hate writing tests, why? * Bad tests are expensive, it takes up a lot of developmental time both in writing tests and fixing tests that are constantly breaking. * I don't think anyone would say that there isn't value in tests. Everyone knows theirs value to them, it's just that it's too time consuming and painful to write and fix them. * It's possible that, through minimal time investment we can create immense value through our tests. If I am able to effectively and efficiently have thorough coverage in the smallest amount of tests possible, then that's great! If I can do that writing tests can actually be enjoyable rather than painful. * The goal of today's presentation is how can we avoid that all together. 1. to make tests pleasant to write and 2. to make them inexpensive * When we used to write tests before we implemented these principles, testing was difficult and painful, but once we did start following these principles writing tests became much more efficient and pleasant and now I actually find testing fun. Knowing that I can write tests that are minimul and highly effective gives me a lot of assurance and peace of mind about a feature that I'm introducing. This is immensely valuable.

Unit Tests: Goals

Thorough

Stable

Fast

Few

Test Incoming Query Messages

Test incoming query messages by making assertions about what they send back

* Test incoming query messages by making assertions about what they send back

Incoming Command Messages

Test incoming command messages by making assertions about direct public side effects

Receiver has sole responsiblity for asserting the result of direct public side effects

* Test incoming command messages by making assertions about direct public side effects * Receiver has sole responsiblity for asserting the result of direct public side effects

Messages sent to self

* Do not test private methods * Do not make **assertions** about their result

Messages sent to self

* this test is redundant. There is no extra test coverage gained here. * If the first test passes we know for certain that the type method is working correctly. * And if the type method is not working there is no way that the first test will pass.

Outgoing Query Messages

class Application
  attr_reader :candidate

  def initialize(candidate)
    @candidate = candidate
  end

  def candidate_name
    "Candidate : " + @candidate.full_name
  end
end

describe "#candidate_name" do
  it "returns candidates full name" do
    candidate = Candidate.new("Serge Backflip")
    application = Application.new(candidate)

    expect(application.candidate_name).
      to eq "Candidate : Serge Backflip"
  end
end

describe "#candidate_name" do
  it "returns candidates full name" do
    candidate = Candidate.new("Serge Backflip")
    application = Application.new(candidate)

    expect(application.candidate_name).
      to eq "Candidate : Serge Backflip"
    # REDUNDANT
    expect(application.candidate.full_name).
      to eq "Serge Backflip"
  end
end

describe "#candidate_name" do
  it "returns candidates full name" do
    candidate = Candidate.new("Serge Backflip")
    application = Application.new(candidate)

    # STILL REDUNDANT
    expect(candidate).to receive(:full_name)

    expect(application.candidate_name).
      to eq "Candidate : Serge Backflip"
  end
end
            

- Over Specification - Adds cost with no benefits

- Binds you to the implementation details of the full_name method

- If a message has no visible public side effects, the sender should not test it. That's the responsibility of the receiving class

* this test is still redundant. * Over specification. Adds cost with no benefits * this only binds you the implementation details of the hiring_manager_name method * we shouldn't care how the full_name method works, if it changes it's implementation the first test will still pass. * If a message has no visible public side effects, the sender should not test it. That's the job of the receiving class * We've been unit testing some things well, but I don't think we're doing this well yet. We should be more mindful of having too many assertions on message sending. * we should only be testing direct public side effects. The goal is to get the most coverage with the least amount of tests. We don't want tests to be expensive. * with all that said, break the rule if it saves money during development. Sometimes there are private methods that we don't have the time and luxury to refactor and they need to be tested. In those cases you can use your best judgement and test them if you need to.

Outgoing Command Messages

class Department
  def initialize(name)
    @name = name
  end

  def update_name(new_name)
    old_name = name
    self.name = new_name
    self.save!

    ChangeLog.create_entry(self, old_name, new_name)
  end
end
            
it "updates name and create a changelog entry" do
  department = Department.new("Web Development")

  expect { department.update_name("Engineering") }.
    to change { department.name }.
      from("Web Development").
      to("Engineering")

  # make an assertion about the db for ChangeLog?
end
            

- Is this Departments responsibility?

* this is an integration test if we test objects outside of this class * it is ChangeLog's responsibility to test this method

Outgoing Command Messages

it "updates name and create a changelog entry" do
  department = Department.new("Web Development")

  # this message MUST get sent
  expect(ChangeLog).to receive(:create_entry).
    with(department, "Web Developent", "Engineering")

  expect { department.update_name("Engineering") }.
    to change { department.name }.
      from("Web Development").
      to("Engineering")
end
            

- Department IS responsible for sending create_entry to the receiver

- The public api DEPENDS on the create_entry message being sent

- Expect to send outgoing command messages

* Department IS responsible for sending create_entry to the receiver * The public api DEPENDS on the create_entry message being sent * Expect to send outgoing command messages * Break rule if side effects are stable and cheap

Can we Mock reliably ?

* A lot of times mocking and stubbing makes people nervous, because they question if there is a lot of mocking all over the test suite, are we really testing anything? Are the tests reliable?

Mocks - assertion that a message is received.

   => Returns nil by default

              expect(User).to receive(:full_name)
            

Stub - No assertion. When this message is received

   => provides a specific return.

              allow(User).to receive(:full_name).and_return('first last')
            

Mock and Stub - Assertion that a message is received

   => provides a specific return.

              expect(User).to receive(:full_name).and_return('first last')
            

Partial Doubles & verifying partial doubles


class Department
  def update_name(new_name)
    old_name = name
    self.name = new_name
    self.save!

    ChangeLog.create_entry(self, old_name, new_name)
  end
end
            
# department_spec.rb
it "updates name and create a changelog entry" do
  department = create(:department)
  allow(ChangeLog).to receive(:create_entry).
    and_return(true)
end

# spec_helper.rb
RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end
            

- Protects against api changes

- Protects against typos

Mocking External Dependencies

class UserEmailer
  def initialize(user)
    @user = user
  end

  def email
    send_email_to(user.email)
  end
end

            
### Factory Girl

it "sends email to user" do
  user = FactoryGirl.create(:user, :email => "serge.backflip@example.com" )
  user_emailer = UserEmailer.new(user)

  expect(user_emailer.email).to deliver_email_to(
    "serge.backflip@example.com"
  )
end

### Test Double

it "sends email to user" do
  user = double("user")
  allow(user).to receive(:email).
    and_return("serge.backflip@example.com")
  user_emailer = UserEmailer.new(user)

  expect(user_emailer.email).to deliver_email_to(
    "serge.backflip@example.com"
  )
end
# mocking external dependencies makes the test faster.
# testing user, is *not* the responsiblity of UserEmailer.
# all we need is an object that responds to email


### Instance Double

it "sends email to user" do
  user = instance_double("User")
  allow(user).to receive(:email).and_return("serge.backflip@example.com")
  user_emailer = UserEmailer.new(user)

  expect(user_emailer.email).to deliver_email_to(
    "serge.backflip@example.com"
  )
end

# if spec fails
# output: "User does not implement: email"

 

            

- Protects against api changes

- class_double works the same way for class methods

Mocking external dependencies with Distant Side Effects

class ApplicationController
  def accept
    # ...

    application.hire(application_details)
  end
end
            

it "accepts application" do
  # ...

  expect(application).to receive(:hire).and_call_original
end
            

Protects against distant side effects that may error out

* Sometimes you have a method with distant side effects which also requires a lot of setup. * An object that calls a method on another object which in turn calls on a method on another object. * Mocking a method like that does not provide assurance that the code will be working. * Although this individual method may be well tested in the application class, how can I be sure that this method will work in this specific setup in this specific circumstance of the controller? I don't. * To get around this, .and_call_original allows us to assert that a method will be sent and then actually call the method so that we can make sure that the string of methods that are called through the .hire method does not error out. This provides some extra assurance and provides greater coverage.

Summary

- Test everything once

- Test the public interface and not the implementation

- Prefer test doubles over factory girl or actual objects

- Prefer instance_double and class_double over doubles

- Understand these "rules" but use your best judgement for more complex test cases

To Be Continued..?

- Controllers and dependency management

- Understanding waiting behavior to avoid finicky capybara specs

- Making selenium/feature specs readable, reliable, and performant