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

0 Comments
10 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,151
On SlideShare
0
From Embeds
0
Number of Embeds
5
Actions
Shares
0
Downloads
82
Comments
0
Likes
10
Embeds 0
No embeds

No notes for slide
  • Short presentation on the background and principles of TDD and BDD.
  • Think about what the code you&amp;#x2019;re writing is supposed to do. Reduces temptation to write speculative code. Executable docs. Forces clean structure (or at least makes poor structure painful).
  • Think about what the code you&amp;#x2019;re writing is supposed to do. Think about interfaces and responsibilities. Think about alternate paths (errors etc).
  • Reduces temptation to write speculative code.
  • Executable docs. (Almost) guaranteed up-to-date.
  • Forces clean structure (or at least makes poor structure painful).
    Catches some regression bugs.
    Makes refactoring safer (meaning you&amp;#x2019;re more likely to do it).
  • Tests help catch regression problems, enable rapid deployment and give you the confidence to refactor. But that&amp;#x2019;s just a benefit of unit testing, not TDD.
  • Not just the evolution of the practice, but steps most people go through in their understanding of TDD.
  • We&amp;#x2019;ve all written code this way, and probably revert to it from time to time!
    Manual testing (either by the developer or someone else).
    Works at a small scale and in the short term, but unmaintainable.
  • Write test cases to prove that the code works.
    Tests provide confidence that we haven&amp;#x2019;t broken anything when making changes.
  • Classic (but not recommended) approach is to create one test case per production class, and one test method per production method.
  • This passes, but doesn&amp;#x2019;t actually call our method. Obviously it&amp;#x2019;s an extreme example, but it illustrates the problem of writing test cases when the code&amp;#x2019;s already there.
  • How do we know the tests are correct?
    Write them first, watch them fail, then write the code and check they pass.
  • This is an improvement, but means you have to have a fairly complete design before you start (or you are restricted to system-level integration tests, rather than unit testing each class/method.
  • This is an improvement, but means you have to have a fairly complete design before you start (or you are restricted to system-level integration tests, rather than unit testing each class/method.
  • This is an improvement, but means you have to have a fairly complete design before you start (or you are restricted to system-level integration tests, rather than unit testing each class/method.
  • Subtle difference from test-first: it&amp;#x2019;s about design more than testing.
    Make small changes, only writing enough to pass a test.
    After each test, refactor code to a clean design.
  • Cycle should repeat at least every few minutes. Sometimes several times per minute.
  • Cycle should repeat at least every few minutes. Sometimes several times per minute.
  • Cycle should repeat at least every few minutes. Sometimes several times per minute.
  • Cycle should repeat at least every few minutes. Sometimes several times per minute.
  • Cycle should repeat at least every few minutes. Sometimes several times per minute.
  • To a large extent, BDD is &amp;#x2018;TDD done well&amp;#x2019;.
  • We&amp;#x2019;re now specifying what the object under test should do, rather than just writing an uninformatively-named method to test it.
  • This is RSpec, a framework specifically designed for BDD.
  • Even if you&amp;#x2019;re only using an xUnit framework, you can extract similar information if you give your test methods descriptive names.
  • In case the Java people were feeling left-out.
  • Rather than guessing what low-level objects we need and building the system up from there, we start with the actual requirement at the outside of the system (user inter, and discover the need for lower-level objects as we go.
  • Integration testing uses a state-based approach. Used for acceptance tests for new features, which then live on as regression tests.
  • Cucumber. Now also available for Java (Cuke4Duke), but see also JBehave and EasyB.
  • Test classes in isolation.
  • When we ask the object to perform a specific action, it should interact in a particular way with its collaborators. But how do we make that test pass if the collaborator classes haven&amp;#x2019;t been written yet?
  • Mocks and stubs.
  • Stubs are used to mimic the behaviour of other parts of the system that you don&amp;#x2019;t want to use for real. Mocks are used to specify interactions. Don&amp;#x2019;t use mocks for library calls etc which you can&amp;#x2019;t control &amp;#x2013; create a thin wrapper instead, and mock calls to that (let the mocks drive the design of the wrapper API).
  • Boundary objects can be tested using a state-based approach, as mocks are obviously not applicable here.
  • Naive example! Also you wouldn&amp;#x2019;t really have this code when you wrote the tests/specs.
  • Record/playback. Advantage: IDE/refactoring support. Disadvantage: doesn&amp;#x2019;t necessarily encourage writing the test first.
  • Specify expectations in advance, then verify afterwards. Advantage: more declarative. Disadvantage: Weird syntax.
  • Specify expectations in advance. Verified automatically.
  • Also known as test spies.
  • Calling methods on mocks is silent. Verify that expected methods were called afterwards.
  • Alternative mocking framework for RSpec. Stub methods out first (on real instances), then verify that expected methods were called afterwards.
  • Testing behaviour means calling public methods and observing what the class under test does. You should not need to use tricks to test private methods or inspect instance variables.
  • Tests should be runnable individually and in any order. If one test depends on data set up by another, this will make it hard to maintain your test suite, and cause puzzling failures. This is what setup and teardown methods are for.
  • If each test only asserts one aspect of behaviour, it makes it obvious what&amp;#x2019;s wrong when it fails. It also prevents the first failure from masking later assertions.
  • Use stubs and mocks to decouple the class under test from the rest of the system.
    If the method under test is relying on a collaborator providing data, that can simply be stubbed &amp;#x2013;&amp;#xA0;there&amp;#x2019;s no need to assert that the query happened. Use mocks to test that the code under test is passing the right messages to its collaborators.
    Beware of going overboard with stubs and mocks. There&amp;#x2019;s a happy medium between not using them at all and over-using them.
    Don&amp;#x2019;t use stubs and mocks in integration tests (other than possibly for interaction with other systems).
  • Listen to the tests. If they&amp;#x2019;re hard to write or brittle, it&amp;#x2019;s probably a sign that the design of the code could be improved.
  • 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

    ×