Test-Driven Development



        Kerry Buckley
Clearing up some
 Misconceptions
It’s all about testing
G !
        ON
It’s all about testing
  W   R
Tests handed to coders
R !
                E
Tests handed G coders
        O N  to

  W  R
Design activity
Design is emergent
Tests drive that design
Why?
Makes you think
Focus
Documentation
Quality
…and tests
Evolution
Dirty Hacking
Automated Testing
Automated Testing
 class Adder
   def add a, b
     a + b
   end
 end

 class AdderTest < Test::Unit::TestCase
   def test...
Are You Really Testing
     Your Code?
  class Adder
    def add a, b
      a + b
    end
  end

  class AdderTest < Test:...
Test-First Development
Test-First Development
Test-First Development



 Start
Test-First Development

         Write
         tests



 Start           Failing
                  tests
Test-First Development

         Write             Write
         tests             code



 Start           Failing      ...
Test-Driven
Development
Test-Driven
Development
Test-Driven
        Development
Clean
code
Test-Driven
        Development
Clean             Failing
code               test
Test-Driven
        Development
Clean                     Failing
code                       test




         All tests p...
Test-Driven
        Development
Clean                           Failing
code                             test



    Refac...
Test-Driven
        Development
Clean                           Failing
code                             test



    Refac...
Three Rules




http://tinyurl.com/threerules
Three Rules
1. Do not write any production code unless it
   is to make a failing unit test pass.




        http://tinyu...
Three Rules
1. Do not write any production code unless it
   is to make a failing unit test pass.
2. Do not write any more...
Three Rules
1. Do not write any production code unless it
   is to make a failing unit test pass.
2. Do not write any more...
State-Based
class DongleTest < Test::Unit::TestCase
  def test_wibble
    # Set up test inputs
    dongle = Dongle.new
   ...
Bottom-Up
Behaviour-Driven
 Development
More Descriptive Test
       Names
class AdderTest < Test::Unit::TestCase
  def test_should_add_two_positive_numbers
    a...
RSpec

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

  it 'can ...
Generated
           Documentation
$ spec -f s adder_spec.rb

An adder
- can add two positive numbers
- can add a positive...
Matchers (RSpec)

@string.should == "foo"

@array.should_not be_empty

@hash.should have_key(:foo)

@object.should be_an_i...
Matchers (HamCrest)

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

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

assertThat(obj, instanceOf(S...
Outside-In
Integration Testing
Describing Features

Feature: Transferring money between two accounts

  Scenario: Simple transfer
    Given an account ca...
Step Implementations
Given /^an account called '(w*)' containing £(d*)$/ do |name, amount|
  @accounts ||= {}
  @accounts[...
Unit Testing
Interaction-Based
Fake Objects




© Matthew Lee High http://www.flickr.com/photos/matthigh/3045096094/
Mocks and Stubs


   Stub   Mock
Boundary Objects
Mocking Patterns
class AccountController
  def transfer from, to, amount
    from.debit amount
    to.credit amount
  end
end




class Acc...
Record → Playback →
    Run → Verify
EasyMock
public void testTransferDebitsSourceAccount() {
  AccountController controller = new AccountController();

  Acco...
Expect → Run → Verify
JMock
public void testTransferDebitsSourceAccount() {
  AccountController controller = new AccountController();
  
  Accou...
RSpec
describe 'Making a transfer' do
  it 'debits the source account' do
    controller = AccountController.new

    from...
Stub → Run → Verify
Mockito

public void testTransferDebitsSourceAccount() {
  AccountController controller = new AccountController();
  
  Ac...
Not-a-Mock
describe 'Making a transfer' do
  it 'debits the source account' do
    controller = AccountController.new

   ...
Other Mock Features
Mix stubs and mocks
  describe 'Peter Petrelli' do
    before do
      @peter = PeterPetrelli.instance
      @claire = moc...
Specify return values

  @object.stub(:foo).and_return 2, 4, 6

  @object.foo   #   =>   2
  @object.foo   #   =>   4
  @o...
Mock or stub class
        methods

@now = Time.now
Time.stub(:now).and_return @now

@object.should_receive(:update_timest...
Specify expected
     number of calls

@object.should_receive(:foo).once

@object.should_receive(:bar).at_least(3).times
Raise exceptions


@object.stub(:foo).and_raise RuntimeError, 'message'
Good Practices
Test Behaviour, not
 Implementation
Keep Tests Independent
One Assertion/
Expectation per Test
Stub and Mock Where
     Appropriate
If Testing is Hard, the
  Design is Probably
        Wrong
Further Reading
Introducing BDD (Dan North)
  http://dannorth.net/introducing-bdd

BDD Introduction
 http://behaviour-driv...
Upcoming SlideShare
Loading in …5
×

TDD

2,164 views

Published on

Yet another rehash of the same old TDD/BDD presentation.

This one was for the BT Adastral Park Software Craftsmanship Mini-Conference on 8 Sepctember 2009.

  • Be the first to comment

TDD

  1. 1. Test-Driven Development Kerry Buckley
  2. 2. Clearing up some Misconceptions
  3. 3. It’s all about testing
  4. 4. G ! ON It’s all about testing W R
  5. 5. Tests handed to coders
  6. 6. R ! E Tests handed G coders O N to W R
  7. 7. Design activity
  8. 8. Design is emergent
  9. 9. Tests drive that design
  10. 10. Why?
  11. 11. Makes you think
  12. 12. Focus
  13. 13. Documentation
  14. 14. Quality
  15. 15. …and tests
  16. 16. Evolution
  17. 17. Dirty Hacking
  18. 18. Automated Testing
  19. 19. 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
  20. 20. 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
  21. 21. Test-First Development
  22. 22. Test-First Development
  23. 23. Test-First Development Start
  24. 24. Test-First Development Write tests Start Failing tests
  25. 25. Test-First Development Write Write tests code Start Failing Done tests
  26. 26. Test-Driven Development
  27. 27. Test-Driven Development
  28. 28. Test-Driven Development Clean code
  29. 29. Test-Driven Development Clean Failing code test
  30. 30. Test-Driven Development Clean Failing code test All tests pass
  31. 31. Test-Driven Development Clean Failing code test Refactor All tests pass
  32. 32. Test-Driven Development Clean Failing code test Refactor All tests pass
  33. 33. Three Rules http://tinyurl.com/threerules
  34. 34. Three Rules 1. Do not write any production code unless it is to make a failing unit test pass. http://tinyurl.com/threerules
  35. 35. Three Rules 1. Do not write any production code unless it is to make a failing unit test pass. 2. Do not write any more of a unit test than is sufficient to fail; and compilation failures are failures. http://tinyurl.com/threerules
  36. 36. Three Rules 1. Do not write any production code unless it is to make a failing unit test pass. 2. Do not write any more of a unit test than is sufficient to fail; and compilation failures are failures. 3. Do not write any more production code than is sufficient to pass the one failing unit test. http://tinyurl.com/threerules
  37. 37. 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
  38. 38. Bottom-Up
  39. 39. Behaviour-Driven Development
  40. 40. 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
  41. 41. RSpec describe 'An adder' do   it 'can add two positive numbers' do     Adder.new.add(2, 2).should == 4   end   it 'can add a positive and a negative number' do     Adder.new.add(4, -2).should == 2   end end
  42. 42. Generated Documentation $ spec -f s adder_spec.rb An adder - can add two positive numbers - can add a positive and a negative number Finished in 0.005493 seconds 2 examples, 0 failures
  43. 43. 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)
  44. 44. Matchers (HamCrest) assertThat(string, equalTo("foo")); assertThat(array, hasItem("bar")); assertThat(obj, instanceOf(String.class)); assertThat(number, greaterThan(42));
  45. 45. Outside-In
  46. 46. Integration Testing
  47. 47. 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
  48. 48. Step Implementations 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
  49. 49. Unit Testing
  50. 50. Interaction-Based
  51. 51. Fake Objects © Matthew Lee High http://www.flickr.com/photos/matthigh/3045096094/
  52. 52. Mocks and Stubs Stub Mock
  53. 53. Boundary Objects
  54. 54. Mocking Patterns
  55. 55. class AccountController   def transfer from, to, amount     from.debit amount     to.credit amount   end end class AccountController {   public void transfer(Account from, Account  to, int amount) {     from.debit(amount);     to.credit(amount);   } }
  56. 56. Record → Playback → Run → Verify
  57. 57. EasyMock public void testTransferDebitsSourceAccount() {   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); }
  58. 58. Expect → Run → Verify
  59. 59. JMock public void testTransferDebitsSourceAccount() {   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(); }
  60. 60. RSpec describe 'Making a transfer' do   it 'debits the source account' do     controller = AccountController.new     from = mock 'from'     to = mock 'to'     to.stub :credit          from.should_receive(:debit).with 42          controller.transfer from, to, 42   end end
  61. 61. Stub → Run → Verify
  62. 62. Mockito public void testTransferDebitsSourceAccount() {   AccountController controller = new AccountController();      Account from = mock(Account.class);   Account to = mock(Account.class);   controller.transfer(from, to, 42);   verify(from).debit(42); }
  63. 63. Not-a-Mock describe 'Making a transfer' do   it 'debits 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
  64. 64. Other Mock Features
  65. 65. Mix stubs and mocks describe 'Peter Petrelli' do   before do     @peter = PeterPetrelli.instance     @claire = mock Cheerleader     @world = mock World     @claire.stub :save     @world.stub :save   end   it 'saves the cheerleader' do     @claire.should_receive :save     @peter.fulfil_destiny   end   it 'saves the world' do     @world.should_receive :save     @peter.fulfil_destiny   end end
  66. 66. Specify return values @object.stub(:foo).and_return 2, 4, 6 @object.foo # => 2 @object.foo # => 4 @object.foo # => 6 @object.foo # => 6
  67. 67. Mock or stub class methods @now = Time.now Time.stub(:now).and_return @now @object.should_receive(:update_timestamp).with @now
  68. 68. Specify expected number of calls @object.should_receive(:foo).once @object.should_receive(:bar).at_least(3).times
  69. 69. Raise exceptions @object.stub(:foo).and_raise RuntimeError, 'message'
  70. 70. Good Practices
  71. 71. Test Behaviour, not Implementation
  72. 72. Keep Tests Independent
  73. 73. One Assertion/ Expectation per Test
  74. 74. Stub and Mock Where Appropriate
  75. 75. If Testing is Hard, the Design is Probably Wrong
  76. 76. 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

×