This document provides an overview of test-driven development (TDD) and behavior-driven development (BDD), beginning with common misconceptions about TDD and how it differs from traditional testing approaches. It discusses how TDD is a design activity that focuses on emergent design and driving design with tests. Key principles of TDD/BDD are explained, such as writing tests before code, the red-green-refactor cycle, and outside-in development. The document also covers test automation, mock objects, different testing tools and frameworks, and best practices for TDD/BDD.
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("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
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 "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
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