The document discusses test-driven development (TDD) and behavior-driven development (BDD). It clarifies that TDD is a design activity, not just about testing, and explains the benefits of TDD such as reducing speculative code and improving quality. It then covers BDD concepts like outside-in development, interaction-based testing, and using mock objects. The document provides examples of testing frameworks like RSpec and practices for writing better tests and mocks.
11. 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
12. 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
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(quot;fooquot;)
dongle.addRemoteResource(quot;http://foo.com/barquot;)
# Exercise functionality under test
dongle.wibble!
# Verify results are as expected
assert_equal(42, dongle.answer)
end
end
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 quot;An adderquot; do
it quot;should add two positive numbersquot; do
Adder.new.add(2, 2).should == 4
end
it quot;should add a positive and a negative numberquot; 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
28. 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
29. 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
34. 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!
36. Mocking Patterns
• Record and 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 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
42. 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
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
• 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!
45. 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!
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