- Maximizing coverage with the fewest tests possible
- Testing everything once
- Testing the interface and not the implementation
- 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 possibleSlow
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.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 backIncoming 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 effectsMessages sent to self
* Do not test private methods * Do not make **assertions** about their resultMessages 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 methodOutgoing 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 cheapMocks - 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.- 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
- Controllers and dependency management
- Understanding waiting behavior to avoid finicky capybara specs
- Making selenium/feature specs readable, reliable, and performant