TDD
Upcoming SlideShare
Loading in...5
×
 

TDD

on

  • 2,610 views

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

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.

Statistics

Views

Total Views
2,610
Views on SlideShare
2,607
Embed Views
3

Actions

Likes
10
Downloads
77
Comments
0

1 Embed 3

http://www.slideshare.net 3

Accessibility

Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • Short presentation on the background and principles of TDD and BDD.
  • Think about what the code you’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’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). <br /> Catches some regression bugs. <br /> Makes refactoring safer (meaning you&#x2019;re more likely to do it).
  • Tests help catch regression problems, enable rapid deployment and give you the confidence to refactor. But that&#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&#x2019;ve all written code this way, and probably revert to it from time to time! <br /> Manual testing (either by the developer or someone else). <br /> Works at a small scale and in the short term, but unmaintainable.
  • Write test cases to prove that the code works. <br /> Tests provide confidence that we haven&#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&#x2019;t actually call our method. Obviously it&#x2019;s an extreme example, but it illustrates the problem of writing test cases when the code&#x2019;s already there.
  • How do we know the tests are correct? <br /> 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&#x2019;s about design more than testing. <br /> Make small changes, only writing enough to pass a test. <br /> 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 &#x2018;TDD done well&#x2019;.
  • We&#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&#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&#x2019;t been written yet?
  • Mocks and stubs.
  • Stubs are used to mimic the behaviour of other parts of the system that you don&#x2019;t want to use for real. Mocks are used to specify interactions. Don&#x2019;t use mocks for library calls etc which you can&#x2019;t control &#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&#x2019;t really have this code when you wrote the tests/specs.
  • Record/playback. Advantage: IDE/refactoring support. Disadvantage: doesn&#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&#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. <br /> If the method under test is relying on a collaborator providing data, that can simply be stubbed &#x2013;&#xA0;there&#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. <br /> Beware of going overboard with stubs and mocks. There&#x2019;s a happy medium between not using them at all and over-using them. <br /> Don&#x2019;t use stubs and mocks in integration tests (other than possibly for interaction with other systems).
  • Listen to the tests. If they&#x2019;re hard to write or brittle, it&#x2019;s probably a sign that the design of the code could be improved.

TDD TDD Presentation Transcript

  • 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_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
  • Test-First Development Start
  • Test-First Development Write tests Start Failing tests
  • Test-First Development Write Write tests code Start Failing Done tests
  • 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 pass
  • Test-Driven Development Clean Failing code test Refactor All tests pass
  • Test-Driven Development Clean Failing code test Refactor All tests pass
  • 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://tinyurl.com/threerules
  • 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
  • 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
  • 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
  • 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 '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
  • 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
  • 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
  • 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
  • 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 AccountController {   public void transfer(Account from, Account  to, int amount) {     from.debit(amount);     to.credit(amount);   } }
  • Record → Playback → Run → Verify
  • 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); }
  • Expect → Run → Verify
  • 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(); }
  • 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
  • Stub → Run → Verify
  • 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); }
  • 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
  • Other Mock Features
  • 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
  • Specify return values @object.stub(:foo).and_return 2, 4, 6 @object.foo # => 2 @object.foo # => 4 @object.foo # => 6 @object.foo # => 6
  • Mock or stub class methods @now = Time.now Time.stub(:now).and_return @now @object.should_receive(:update_timestamp).with @now
  • 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-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