Slide 1

Slide 1 text

Lets talk about testing... Chris McGrath @chrismcg Thursday, 22 August 13

Slide 2

Slide 2 text

Assumptions • We’re talking about business code, most probably Rails code • We like to have fun with our work • Code that’s easy to change is the most fun to work on • Tests should make our code easier to change Thursday, 22 August 13

Slide 3

Slide 3 text

Why we test • Fix bugs and prevent regressions • Improve design and expose design flaws • Provide documentation • Defer design decisions • Support abstractions Thursday, 22 August 13

Slide 4

Slide 4 text

Why we test • Reduce Costs • Prove the app does what we think it does Thursday, 22 August 13

Slide 5

Slide 5 text

Change • OO code is easiest to change when each class has • A single responsibility • As few dependencies as possible • See POODR Thursday, 22 August 13

Slide 6

Slide 6 text

Dependency when an object • Knows the name of another class • Knows the name of a message it intends to send to someone other than self • Knows the arguments a message requires • Knows the order of those arguments Thursday, 22 August 13

Slide 7

Slide 7 text

Tests • Depend on the code they test (duh!) • We want to minimise dependencies • So we want as few tests as possible • And they should test behaviour rather than implementation Thursday, 22 August 13

Slide 8

Slide 8 text

Two approaches to testing • State verification tests (black box) • Interaction verification (white box / mocks) • Prefer state as it doesn't couple you to implementation • Mockist style can be great when don't know the end state • You’ll end up using both Thursday, 22 August 13

Slide 9

Slide 9 text

Types of test • Acceptance • Integration • Unit Thursday, 22 August 13

Slide 10

Slide 10 text

Cost of tests • Time they take to run • Time they take to write • Cost of changing tests when dependent code changes Thursday, 22 August 13

Slide 11

Slide 11 text

Costs • What’s the cost of writing that test • What’s the cost of not writing that test • How critical is your software Thursday, 22 August 13

Slide 12

Slide 12 text

Testing drives design • So don’t need to test design that is already setup for you • e.g. anything Rails • Tests should be telling you something Thursday, 22 August 13

Slide 13

Slide 13 text

1 class Project < ActiveRecord::Base 2 has_many :users 3 has_one :owner 4 5 validates_presence_of :name 6 validates_presence_of :owner 7 scope :active, where(deleted: false) 8 end Some Code Thursday, 22 August 13

Slide 14

Slide 14 text

Bad Tests (imho) 9 # bad 10 describe Project do 11 it { should have_many :users } 12 it { should validate_presence_of :owner } 13 it { should validate_presence_of :name } 14 it { should have_scope(:active).where(deleted: false) } 15 end • Don’t tell me anything or drive design • Completely coupled to implementation Thursday, 22 August 13

Slide 15

Slide 15 text

Good Tests (imo) 19 describe Project, "#name" do 20 it "is required" do 21 expect(subject.errors.on(:name)).to eql(I18n.t('errors.messages.empty')) 22 end 23 end 24 25 describe Project, ".active" do 26 it "doesn't return deleted projects" do 27 live = FactoryGirl.create(:project) 28 FactoryGirl.create(:project, deleted: false) 29 expect(Project.active).to eq([live]) 30 end 31 end • Don’t test things that other tests should cover • Do test things that are important (imho) Thursday, 22 August 13

Slide 16

Slide 16 text

What to write • Golden path acceptance test for the critical part of your application • Integration tests for every important feature • Unit tests for classes you create yourself to document public apis Thursday, 22 August 13

Slide 17

Slide 17 text

Acceptance test 1 # encoding: utf-8 2 require 'spec_helper' 3 4 describe "Billing" do 5 let(:email) { “[email protected]” } 6 7 before do 8 @student = create_student email 9 end 10 11 it "allows the student to add funds" do 12 add_funds(50, @student.email) 13 within "#user_balance" do 14 page.should have_text("€50") 15 end 16 end 17 end 18 Thursday, 22 August 13

Slide 18

Slide 18 text

Integration tests • Just a check for something you’d expect whenever everything is wired up correctly • Don’t use them as unit tests (testing for negatives) • Don’t use them as acceptance tests Thursday, 22 August 13

Slide 19

Slide 19 text

Shared integration tests 1 shared_examples_for "student only action" do |action| 2 let(:tutor) { FactoryGirl.create(:tutor) } 3 let(:student) { FactoryGirl.create(:student) } 4 5 before do 6 activate_authlogic 7 end 8 9 it "redirects when no user" do 10 do_action 11 response.should redirect_to("/user_sessions/new") 12 end 13 14 it "redirects when user is tutor" do 15 UserSession.create(tutor) 16 do_action 17 response.should redirect_to("/") 18 flash[:error].should == "That page is for students only" 19 end 20 21 it "performs action when user is a student" do 22 UserSession.create(student) 23 do_action 24 flash[:error].should be_nil 25 end 26 end Thursday, 22 August 13

Slide 20

Slide 20 text

Shared integration specs 3 describe Settings::BillingController, "#show" do 4 def do_action 5 get :show 6 end 7 8 it_behaves_like "student only action" 9 end Thursday, 22 August 13

Slide 21

Slide 21 text

Acceptance, Integration, or Unit? 1 Feature: Adding a translation from the command line 2 3 Scenario: Running add 4 In order to add a key and translation content 5 When I have a valid project on localeapp.com with api key "MYAPIKEY" 6 And an initializer file 7 When I run `localeapp add foo.baz en:"test en content" es:"test es content"` 8 Then the output should contain: 9 """ 10 Localeapp Add 11 12 Sending key: foo.baz 13 Success! 14 """ 15 16 Scenario: Running add with no arguments 17 In order to add a key and translation content 18 When I have a valid project on localeapp.com with api key "MYAPIKEY" 19 And an initializer file 20 When I run `localeapp add` 21 Then the output should contain: 22 """ 23 localeapp add requires a key name and at least one translation 24 """ Thursday, 22 August 13

Slide 22

Slide 22 text

Views • If they need tests they’re too complicated • Move logic out into presenter style class and test that • Integration test should be enough to show everything is wired together Thursday, 22 August 13

Slide 23

Slide 23 text

Controllers • Shouldn’t be doing any work themselves • Move logic out into domain logic classes and test those • Integration tests enough to show everything is wired together correctly • Shared tests to check security etc. Thursday, 22 August 13

Slide 24

Slide 24 text

Unit Tests • Stealing next slide from Sandi Metz • Really... Just go watch her video from RailsConf 2013 • “Magic Tricks of Testing” Thursday, 22 August 13

Slide 25

Slide 25 text

Query Command Assert result Assert direct public side e ects Ignore Expect to send The Unit Testing Minimalist Incoming Type @sandimetz Apr 2013 Message Ignore Sent to Self Outgoing Origin Saturday, April 27, 13 Thursday, 22 August 13

Slide 26

Slide 26 text

Use VCR • Recording allows you to keep your tests fast • Have an easy to way to remove recordings so you can always run against the endpoint in CI (if this makes sense) Thursday, 22 August 13

Slide 27

Slide 27 text

Tools I use • RSpec for unit tests • RSpec, Capybara, and Poltergeist for integration tests • VCR for testing API integration • Timecop for when time is important • Don’t have a go to for acceptance tests. Trying Mechanize, PhantomJS, bash & curl Thursday, 22 August 13

Slide 28

Slide 28 text

Other tools you might like • test/unit - Built into ruby • cucumber • capybara-webkit • ... a million more ... Thursday, 22 August 13

Slide 29

Slide 29 text

Resources • http://betterspecs.org • http://devblog.songkick.com/2012/07/16/ from-15-hours-to-15-seconds-reducing-a- crushing-build-time/ • http://lanyrd.com/2013/railsconf/scgrbx/ #link-rkxx (Sandi Metz’s testing talk) Thursday, 22 August 13