tdd, bdd and mocks
TRANSCRIPT
Test-Behaviour-
Kerry Buckley
Driven Development}
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 Testingclass Adder def add a, b a + b endend
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) endend
Are You Really Testing Your Code?
class Adder def add a, b a + b endend
class AdderTest < Test::Unit::TestCase def test_add assert_equal 4, 2 + 2 endend
Test-First Development
Test-First Development
Failingtests
Start Done
Writecode
Writetests
Test-Driven Development
Test-Driven Development
Failingtest
Cleancode
All tests pass
Refactor
State-Basedclass 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) endend
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) endend
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 endend
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_iend
Then /^the '(\w*)' account should contain £(\d*)$/ do |name, amount| @@accounts[name].balance.should == amount.to_iend
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 Patternsclass 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 endend
EasyMockpublic 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);}
JMockMockery 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 endend
Not-a-Mockdescribe '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) endend
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 ReadingIntroducing BDD (Dan North)
http://dannorth.net/introducing-bdd
BDD Introductionhttp://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