Test-
Behaviour-   } Driven Development
              Kerry Buckley
Clearing up some
 Misconceptions
TDD is not about
    testing
TDD does not mean
 handing acceptance
 tests to developers
TDD is a design activity
Software design is
emergent, and happens
 during development
Why TDD?

• Makes you think about required behaviour
• Reduces speculative code
• Provides documentation
• Improves quality
Evolution
Dirty Hacking
Automated Testing
Automated Testing
 class Adder
   def add a, b
     a + b
   end
 end

 class AdderTest < Test::Unit::TestCase
   def test_add
     adder = Adder.new
     assert_equal 4, adder.add(2, 2)
     assert_equal 2, adder.add(4, -2)
   end
 end
Are You Really Testing
     Your Code?
  class Adder
    def add a, b
      a + b
    end
  end

  class AdderTest < Test::Unit::TestCase
    def test_add
      assert_equal 4, 2 + 2
    end
  end
Test-First Development
Test-First Development

         Write             Write
         tests             code



 Start           Failing           Done
                  tests
Test-Driven
Development
Test-Driven
        Development
Clean                           Failing
code                             test



    Refactor



               All tests pass
State-Based
class DongleTest < Test::Unit::TestCase
  def test_wibble
    # Set up test inputs
    dongle = Dongle.new
    dongle.addString("foo")
    dongle.addRemoteResource("http://foo.com/bar")

   # Exercise functionality under test
   dongle.wibble!

    # Verify results are as expected
    assert_equal(42, dongle.answer)
  end
end
Bottom-Up
Behaviour-Driven
 Development
Behaviour-Driven
      Development
Verification    Specification

State-based    Interaction-based

Bottom-up      Outside-in

Testing tool   Design tool

  Invention    Discovery
More Descriptive Test
       Names
class AdderTest < Test::Unit::TestCase
  def test_should_add_two_positive_numbers
    assert_equal 4, Adder.new.add(2, 2)
  end

  def test_should_add_a_positive_and_a_negative_number
    assert_equal 2, Adder.new.add(4, -2)
  end
end
RSpec

describe "An adder" do
  it "should add two positive numbers" do
    Adder.new.add(2, 2).should == 4
  end

  it "should add a positive and a negative number" do
    Adder.new.add(4, -2).should == 2
  end
end
Generated
         Documentation
$ spec -f s adder_spec.rb

An adder
- should add two positive numbers
- should add a positive and a negative number

Finished in 0.005493 seconds

2 examples, 0 failures
Matchers (RSpec)

@string.should == "foo"

@array.should_not be_empty

@hash.should have_key(:foo)

@object.should be_an_instance_of String

lambda { @stack.pop }.should raise_error(StackUnderflowError)
Matchers (HamCrest)

assertThat(string, equalTo("foo"));

assertThat(array, hasItem("bar"));

assertThat(obj, instanceOf(String.class));

assertThat(number, greaterThan(42));
Outside-In
Integration Testing
Describing Features

Feature: Transferring money between two accounts

  Scenario: Simple transfer
    Given an account called 'source' containing £100
    And an account called 'destination' containing £50
    When I transfer £20 from source to destination
    Then the 'source' account should contain £80
    And the 'destination' account should contain £70
Describing Features
Given /^an account called '(w*)' containing £(d*)$/ do |name, amount|
  @@accounts ||= {}
  @@accounts[name] = Account.new(amount.to_i)
end

When /^I transfer £(d*) from (w*) to (w*)$/ do |amount, from, to|
  AccountController.new.transfer @@accounts[from], @@accounts[to], amount.to_i
end

Then /^the '(w*)' account should contain £(d*)$/ do |name, amount|
  @@accounts[name].balance.should == amount.to_i
end
Unit Testing
Interaction-Based
Mock Objects


 Mock   Mock
Classicists v Mockists
Mock Objects

• Stand-ins for collaborating objects
• Mock the interface, not a specific object
• Verify that expected calls are made
• Not stubs!
• For your code only!
Boundary Objects
Mocking Patterns

• Record and playback
• Specify expectations before running
• Check expectations after running
Mocking Patterns
class AccountController {
  public void transfer(Account from, Account to, int amount) {
    from.debit(amount);
    to.credit(amount);
  }
}


class AccountController
  def transfer from, to, amount
    from.debit amount
    to.credit amount
  end
end
EasyMock
public void testTransferShouldDebitSourceAccount() {
  AccountController controller = new AccountController();

    Account from = createMock(Account.class);
    Account to = createNiceMock(Account.class);
    from.debit(42);

    replay(from);
    replay(to);

    controller.transfer(from, to, 42);

    verify(from);
}
JMock
Mockery context = new Mockery();

public void testTransferShouldDebitSourceAccount() {
  AccountController controller = new AccountController();

    Account from = context.mock(Account.class);
    Account to = context.mock(Account.class);

    context.checking(new Expectations() {{
      oneOf(from).debit(42);
    }});

    controller.transfer(from, to, 42);

    context.assertIsSatisfied();
}
Mockito
public void testTransferShouldDebitSourceAccount() {
  AccountController controller = new AccountController();

    Account from = mock(Account.class);
    Account to = mock(Account.class);

    controller.transfer(from, to, 42);

    verify(from).debit(42);
}
RSpec
describe 'Making a transfer' do
  it 'should debit the source account' do
    controller = AccountController.new

   from = stub_everything 'from'
   to = stub_everything 'to'

   from.should_receive(:debit).with 42

    controller.transfer from, to, 42
  end
end
Not-a-Mock
describe 'Making a transfer' do
  it 'should debit the source account' do
    controller = AccountController.new
    from = Account.new
    to = Account.new

   from.stub_method :debit => nil
   to.stub_method :credit => nil

   controller.transfer from, to, 42

    from.should_have_received(:debit).with(42)
  end
end
Other Mock Features
• Stubs (for when you don’t care)
• Mock class/static methods
• Specify return values
• Specify expected number of calls
• Specify method ordering
• Raise exceptions on method calls
Good Practices
• Test behaviour, not implementation
• One expectation per test
• Don’t test private methods
• Don’t mock everything
• Stub queries; mock actions
• Tell, don’t ask
• Listen to test smells!
TDD/BDD Summary
• Never write any code without a failing test
• Start from the outside, with acceptance tests
• Drive design inwards using mock objects
• Tests should be descriptive specifications
• Red – Green – Refactor
• YAGNI
• TATFT!
Further Reading
Introducing BDD (Dan North)
  http://dannorth.net/introducing-bdd

BDD Introduction
 http://behaviour-driven.org/Introduction

Mock Roles, Not Objects
(Freeman, Mackinnon, Pryce, Walnes)
  http://www.jmock.org/oopsla2004.pdf

BDD in Ruby (Dave Astels)
 http://blog.daveastels.com/files/BDD_Intro.pdf

Test all the F***in Time (Brian Liles, video)
 http://www.icanhaz.com/tatft

Bdd for-dso-1227123516572504-8

  • 1.
    Test- Behaviour- } Driven Development Kerry Buckley
  • 2.
    Clearing up some Misconceptions
  • 3.
    TDD is notabout testing
  • 4.
    TDD does notmean handing acceptance tests to developers
  • 5.
    TDD is adesign activity
  • 6.
    Software design is emergent,and happens during development
  • 7.
    Why TDD? • Makesyou think about required behaviour • Reduces speculative code • Provides documentation • Improves quality
  • 8.
  • 9.
  • 10.
  • 11.
    Automated Testing classAdder def add a, b a + b end end class AdderTest < Test::Unit::TestCase def test_add adder = Adder.new assert_equal 4, adder.add(2, 2) assert_equal 2, adder.add(4, -2) end end
  • 12.
    Are You ReallyTesting Your Code? class Adder def add a, b a + b end end class AdderTest < Test::Unit::TestCase def test_add assert_equal 4, 2 + 2 end end
  • 13.
  • 14.
    Test-First Development Write Write tests code Start Failing Done tests
  • 15.
  • 16.
    Test-Driven Development Clean Failing code test Refactor All tests pass
  • 17.
    State-Based class DongleTest <Test::Unit::TestCase def test_wibble # Set up test inputs dongle = Dongle.new dongle.addString("foo") dongle.addRemoteResource("http://foo.com/bar") # Exercise functionality under test dongle.wibble! # Verify results are as expected assert_equal(42, dongle.answer) end end
  • 18.
  • 19.
  • 20.
    Behaviour-Driven Development Verification Specification State-based Interaction-based Bottom-up Outside-in Testing tool Design tool Invention Discovery
  • 21.
    More Descriptive Test Names class AdderTest < Test::Unit::TestCase def test_should_add_two_positive_numbers assert_equal 4, Adder.new.add(2, 2) end def test_should_add_a_positive_and_a_negative_number assert_equal 2, Adder.new.add(4, -2) end end
  • 22.
    RSpec describe "An adder"do it "should add two positive numbers" do Adder.new.add(2, 2).should == 4 end it "should add a positive and a negative number" do Adder.new.add(4, -2).should == 2 end end
  • 23.
    Generated Documentation $ spec -f s adder_spec.rb An adder - should add two positive numbers - should add a positive and a negative number Finished in 0.005493 seconds 2 examples, 0 failures
  • 24.
    Matchers (RSpec) @string.should =="foo" @array.should_not be_empty @hash.should have_key(:foo) @object.should be_an_instance_of String lambda { @stack.pop }.should raise_error(StackUnderflowError)
  • 25.
    Matchers (HamCrest) assertThat(string, equalTo("foo")); assertThat(array,hasItem("bar")); assertThat(obj, instanceOf(String.class)); assertThat(number, greaterThan(42));
  • 26.
  • 27.
  • 28.
    Describing Features Feature: Transferringmoney between two accounts Scenario: Simple transfer Given an account called 'source' containing £100 And an account called 'destination' containing £50 When I transfer £20 from source to destination Then the 'source' account should contain £80 And the 'destination' account should contain £70
  • 29.
    Describing Features Given /^anaccount called '(w*)' containing £(d*)$/ do |name, amount| @@accounts ||= {} @@accounts[name] = Account.new(amount.to_i) end When /^I transfer £(d*) from (w*) to (w*)$/ do |amount, from, to| AccountController.new.transfer @@accounts[from], @@accounts[to], amount.to_i end Then /^the '(w*)' account should contain £(d*)$/ do |name, amount| @@accounts[name].balance.should == amount.to_i end
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
    Mock Objects • Stand-insfor collaborating objects • Mock the interface, not a specific object • Verify that expected calls are made • Not stubs! • For your code only!
  • 35.
  • 36.
    Mocking Patterns • Recordand playback • Specify expectations before running • Check expectations after running
  • 37.
    Mocking Patterns class AccountController{ public void transfer(Account from, Account to, int amount) { from.debit(amount); to.credit(amount); } } class AccountController def transfer from, to, amount from.debit amount to.credit amount end end
  • 38.
    EasyMock public void testTransferShouldDebitSourceAccount(){ AccountController controller = new AccountController(); Account from = createMock(Account.class); Account to = createNiceMock(Account.class); from.debit(42); replay(from); replay(to); controller.transfer(from, to, 42); verify(from); }
  • 39.
    JMock Mockery context =new Mockery(); public void testTransferShouldDebitSourceAccount() { AccountController controller = new AccountController(); Account from = context.mock(Account.class); Account to = context.mock(Account.class); context.checking(new Expectations() {{ oneOf(from).debit(42); }}); controller.transfer(from, to, 42); context.assertIsSatisfied(); }
  • 40.
    Mockito public void testTransferShouldDebitSourceAccount(){ AccountController controller = new AccountController(); Account from = mock(Account.class); Account to = mock(Account.class); controller.transfer(from, to, 42); verify(from).debit(42); }
  • 41.
    RSpec describe 'Making atransfer' do it 'should debit the source account' do controller = AccountController.new from = stub_everything 'from' to = stub_everything 'to' from.should_receive(:debit).with 42 controller.transfer from, to, 42 end end
  • 42.
    Not-a-Mock describe 'Making atransfer' do it 'should debit the source account' do controller = AccountController.new from = Account.new to = Account.new from.stub_method :debit => nil to.stub_method :credit => nil controller.transfer from, to, 42 from.should_have_received(:debit).with(42) end end
  • 43.
    Other Mock Features •Stubs (for when you don’t care) • Mock class/static methods • Specify return values • Specify expected number of calls • Specify method ordering • Raise exceptions on method calls
  • 44.
    Good Practices • Testbehaviour, not implementation • One expectation per test • Don’t test private methods • Don’t mock everything • Stub queries; mock actions • Tell, don’t ask • Listen to test smells!
  • 45.
    TDD/BDD Summary • Neverwrite any code without a failing test • Start from the outside, with acceptance tests • Drive design inwards using mock objects • Tests should be descriptive specifications • Red – Green – Refactor • YAGNI • TATFT!
  • 46.
    Further Reading Introducing BDD(Dan North) http://dannorth.net/introducing-bdd BDD Introduction http://behaviour-driven.org/Introduction Mock Roles, Not Objects (Freeman, Mackinnon, Pryce, Walnes) http://www.jmock.org/oopsla2004.pdf BDD in Ruby (Dave Astels) http://blog.daveastels.com/files/BDD_Intro.pdf Test all the F***in Time (Brian Liles, video) http://www.icanhaz.com/tatft