Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lets Talk About Testing

Lets Talk About Testing

My (current) thoughts on testing and how I test

Chris McGrath

August 20, 2013
Tweet

More Decks by Chris McGrath

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. Why we test • Reduce Costs • Prove the app

    does what we think it does Thursday, 22 August 13
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. Other tools you might like • test/unit - Built into

    ruby • cucumber • capybara-webkit • ... a million more ... Thursday, 22 August 13