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. Lets talk about testing...
    Chris McGrath
    @chrismcg
    Thursday, 22 August 13

    View Slide

  2. 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

    View Slide

  3. 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

    View Slide

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

    View Slide

  5. 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

    View Slide

  6. 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

    View Slide

  7. 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

    View Slide

  8. 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

    View Slide

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

    View Slide

  10. 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

    View Slide

  11. 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

    View Slide

  12. 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

    View Slide

  13. 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

    View Slide

  14. 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

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

  17. 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

    View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. 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

    View Slide

  21. 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

    View Slide

  22. 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

    View Slide

  23. 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

    View Slide

  24. 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

    View Slide

  25. 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

    View Slide

  26. 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

    View Slide

  27. 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

    View Slide

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

    View Slide

  29. 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

    View Slide