Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

The Craft of TDD

The Craft of TDD

This talk was given at PixelsCamp 2016 on the 7th of October. It isn't aimed for the complete novice, it's not really a "how-to", instead it's aimed at those that have heard and looked into TDD and that might have tried it out but gave up because it takes time or didn't initially find it valuable. There will also be things for those that do TDD and are continuing to learn about it.

At the end of the talk I hope to convince you that TDD is actually quite useful, and that you leave with some awareness on how to make writing these tests a bit easier while avoiding some common traps as you continue on this path.

List of recommended resources from the talk: http://bit.ly/the-craft-of-tdd

Avatar for Raoul Felix

Raoul Felix

October 07, 2016
Tweet

Other Decks in Programming

Transcript

  1. deliver value to our users deliver working software faster adapt

    to user’s changes produce quality software
  2. it “is a productive workflow” do describe “Benefits of TDD”

    do context “delivering software faster” do
  3. describe “Benefits of TDD” do context “delivering software faster” do

    it “is a productive workflow” do green refactor red
  4. write code refactor write test describe “Benefits of TDD” do

    context “delivering software faster” do it “is a productive workflow” do
  5. expect( developer.in_the_zone?). to eq(true) describe “Benefits of TDD” do context

    “delivering software faster” do it “is a productive workflow” do
  6. .F Failures: 1) App.current_username is nil by default Failure/Error: expect(App.current_username).to

    be_nil expected: nil got: "[email protected]" # ./tip_fix_flaky_tests_spec.rb:29:in `block (3 levels) in <top (required)>' Finished in 0.02265 seconds (files took 0.10097 seconds to load) 2 examples, 1 failure Failed examples: rspec ./tip_fix_flaky_tests_spec.rb:28 # App.current_username is nil by default rspec spec/ describe “Benefits of TDD” do context “delivering software faster” do it “is a productive workflow” do
  7. it “facilitates working in teams” do describe “Benefits of TDD”

    do context “delivering software faster” do
  8. code as documentation describe “Benefits of TDD” do context “delivering

    software faster” do it “facilitates working in teams” do
  9. detect conflicts in changes describe “Benefits of TDD” do context

    “delivering software faster” do it “facilitates working in teams” do
  10. catch bugs earlier describe “Benefits of TDD” do context “delivering

    software faster” do it “lowers maintenance costs” do
  11. debug issues faster describe “Benefits of TDD” do context “delivering

    software faster” do it “lowers maintenance costs” do
  12. $ rspec --format documentation tdd_benefits_spec.rb:9 Benefits of TDD delivering software

    faster is a productive workflow lowers maintenance costs facilitates working in teams Finished in 5 minutes and 0.64148 seconds 3 examples, 0 failures
  13. it “helps produce a better ” do design describe “Benefits

    of TDD” do context “adapting to user’s changes” do
  14. tests are users of your “API” describe “Benefits of TDD”

    do context “adapting to user’s changes” do it “helps produce a better design” do
  15. refactor if pain? describe “Benefits of TDD” do context “adapting

    to user’s changes” do it “helps produce a better design” do
  16. less coupling describe “Benefits of TDD” do context “adapting to

    user’s changes” do it “helps produce a better design” do
  17. immediate feedback describe “Benefits of TDD” do context “adapting to

    user’s changes” do it “helps produce a better design” do
  18. confidence to change the design describe “Benefits of TDD” do

    context “adapting to user’s changes” do it “helps produce a better design” do
  19. it “doesn’t guarantee a good ” do design describe “Benefits

    of TDD” do context “adapting to user’s changes” do
  20. BDUF design no describe “Benefits of TDD” do context “adapting

    to user’s changes” do it “doesn’t guarantee a good design” do
  21. BDUF design no design emergent describe “Benefits of TDD” do

    context “adapting to user’s changes” do it “doesn’t guarantee a good design” do
  22. BDUF design no design emergent not magic requires thinking describe

    “Benefits of TDD” do context “adapting to user’s changes” do it “doesn’t guarantee a good design” do
  23. “Act locally, think globally” Ron Jefferies co-author of XP and

    an Agile Manifesto signatory describe “Benefits of TDD” do context “adapting to user’s changes” do it “doesn’t guarantee a good design” do
  24. describe “good ” do design describe “Benefits of TDD” do

    context “adapting to user’s changes” do
  25. it “follows S.O.L.I.D. principles” do describe “Benefits of TDD” do

    context “adapting to user’s changes” do describe “good design” do
  26. describe “Benefits of TDD” do context “adapting to user’s changes”

    do describe “good design” do it “follows S.O.L.I.D. principles” do S - Single Responsibility O - Open Closed L - Liskov Substitution I - Interface Segregation D - Dependency Inversion
  27. describe “Benefits of TDD” do context “adapting to user’s changes”

    do describe “good design” do it “follows S.O.L.I.D. principles” do S - Single Responsibility O - Open Closed L - Liskov Substitution I - Interface Segregation D - Dependency Inversion
  28. require 'httparty' class IP OK_CODE = 200 IP_SERVICE = 'http://bot.whatismyipaddress.com'

    def even_or_odd response = HTTParty.get(IP_SERVICE) return unless response.code == OK_CODE ip = response.body.strip if ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end ip = IP.new puts "My IP is #{ip.even_or_odd}"
  29. require 'httparty' class IP OK_CODE = 200 IP_SERVICE = 'http://bot.whatismyipaddress.com'

    def even_or_odd response = HTTParty.get(IP_SERVICE) return unless response.code == OK_CODE ip = response.body.strip if ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end ip = IP.new puts "My IP is #{ip.even_or_odd}"
  30. require 'httparty' class IP OK_CODE = 200 IP_SERVICE = 'http://bot.whatismyipaddress.com'

    def even_or_odd response = HTTParty.get(IP_SERVICE) return unless response.code == OK_CODE ip = response.body.strip if ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end ip = IP.new puts "My IP is #{ip.even_or_odd}"
  31. require 'httparty' class IP OK_CODE = 200 IP_SERVICE = 'http://bot.whatismyipaddress.com'

    def even_or_odd response = HTTParty.get(IP_SERVICE) return unless response.code == OK_CODE ip = response.body.strip if ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end ip = IP.new puts "My IP is #{ip.even_or_odd}"
  32. require 'httparty' class IP OK_CODE = 200 IP_SERVICE = 'http://bot.whatismyipaddress.com'

    def even_or_odd response = HTTParty.get(IP_SERVICE) return unless response.code == OK_CODE ip = response.body.strip if ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end ip = IP.new puts "My IP is #{ip.even_or_odd}"
  33. class WhatIsMyIPAddress OK_CODE = 200 IP_SERVICE = 'http://bot.whatismyipaddress.com' def my_ip

    response = HTTParty.get(IP_SERVICE) return unless response.code == OK_CODE response.body.strip end end class ClassifyIP def self.call public_ip = WhatIsMyIPAddress.new.my_ip if public_ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end
  34. class WhatIsMyIPAddress OK_CODE = 200 IP_SERVICE = 'http://bot.whatismyipaddress.com' def my_ip

    response = HTTParty.get(IP_SERVICE) return unless response.code == OK_CODE response.body.strip end end class ClassifyIP def self.call public_ip = WhatIsMyIPAddress.new.my_ip if public_ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end
  35. describe “Benefits of TDD” do context “adapting to user’s changes”

    do describe “good design” do it “follows S.O.L.I.D. principles” do S - Single Responsibility O - Open Closed L - Liskov Substitution I - Interface Segregation D - Dependency Inversion
  36. class ClassifyIP def self.call public_ip = WhatIsMyIPAddress.new.my_ip if public_ip.split('.').last.to_i %

    2 == 0 'even' else 'odd' end end end describe ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do expect(ClassifyIP.call).to eq('even') end end end
  37. class ClassifyIP def self.call public_ip = WhatIsMyIPAddress.new.my_ip if public_ip.split('.').last.to_i %

    2 == 0 'even' else 'odd' end end end describe ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do expect(ClassifyIP.call).to eq('even') end end end
  38. class ClassifyIP def self.call public_ip = WhatIsMyIPAddress.new.my_ip if public_ip.split('.').last.to_i %

    2 == 0 'even' else 'odd' end end end describe ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do expect(ClassifyIP.call).to eq('even') end end end
  39. class ClassifyIP def self.call(ip_service = WhatIsMyIpAddress.new) public_ip = ip_service.my_ip if

    public_ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end describe ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do expect(ClassifyIP.call).to eq('even') end end end
  40. class ClassifyIP def self.call(ip_service = WhatIsMyIpAddress.new) public_ip = ip_service.my_ip if

    public_ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end describe ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do stub_ip_service = double('Stub IP Service’, my_ip: ‘192.168.2.2') expect( ClassifyIP.call(ip_service: stub_ip_service) ).to eq('even') end end end
  41. it “follows 4 Rules of Simple Design” do describe “Benefits

    of TDD” do context “adapting to user’s changes” do describe “good design” do
  42. 1 - Passes all tests describe “Benefits of TDD” do

    context “adapting to user’s changes” do describe “good design” do it “follows 4 Rules of Simple Design” do
  43. 1 2 - Passes all tests - Reveals intention describe

    “Benefits of TDD” do context “adapting to user’s changes” do describe “good design” do it “follows 4 Rules of Simple Design” do
  44. 3 - No duplication describe “Benefits of TDD” do context

    “adapting to user’s changes” do describe “good design” do it “follows 4 Rules of Simple Design” do 1 2 - Passes all tests - Reveals intention
  45. 4 - Fewest elements describe “Benefits of TDD” do context

    “adapting to user’s changes” do describe “good design” do it “follows 4 Rules of Simple Design” do 3 - No duplication 1 2 - Passes all tests - Reveals intention
  46. it “favours separation of concerns” do describe “Benefits of TDD”

    do context “adapting to user’s changes” do describe “good design” do
  47. e.g. The Clean Architecture describe “Benefits of TDD” do context

    “adapting to user’s changes” do describe “good design” do it “favours separation of concerns” do
  48. e.g. Hexagonal Architecture describe “Benefits of TDD” do context “adapting

    to user’s changes” do describe “good design” do it “favours separation of concerns” do app layer domain model user-side adaptors data-side adaptors ui http … db service …
  49. $ rspec --format documentation tdd_benefits_spec.rb:24 Benefits of TDD adapting to

    user’s changes helps produce a better design it doesn’t guarantee a good design good design follows S.O.L.I.D. principles follows 4 Rules of Simple Design favours separation of concerns Finished in 10 minutes and 0.64148 seconds 5 examples, 0 failures
  50. it “helps when writing tests first” do describe “Benefits of

    TDD” do context “producing quality software” do
  51. focused thinking describe “Benefits of TDD” do context “producing quality

    software” do it “helps when writing tests first” do
  52. you define what you want describe “Benefits of TDD” do

    context “producing quality software” do it “helps when writing tests first” do
  53. avoid fragile tests describe “Benefits of TDD” do context “producing

    quality software” do it “helps when writing tests first” do
  54. it “results in a test suite” do describe “Benefits of

    TDD” do context “producing quality software” do
  55. 1 - Classical / Detroit Style TDD describe “Benefits of

    TDD” do context “producing quality software” do it “results in a test suite” do unit real collaborator
  56. describe SayHello do it "returns a greeting using a formatted

    name" do expect(SayHello.call("John")).to eq("Hello there JOHN!") end end class SayHello def self.call(name) capitalized_name = CapitalizeFormatter.new.format(name) "Hello there #{capitalized_name}!" end end class CapitalizeFormatter def format(name) name.upcase end end
  57. describe SayHello do it "returns a greeting using a formatted

    name" do expect(SayHello.call("John")).to eq("Hello there JOHN!") end end class SayHello def self.call(name) "Hello there #{name.upcase}!” end end class CapitalizeFormatter def format(name) name.upcase end end
  58. describe SayHello do it "returns a greeting using a formatted

    name" do expect(SayHello.call("John")).to eq("Hello there JOHN!") end end class SayHello def self.call(name) capitalized_name = CapitalizeFormatter.new.format(name) "Hello there #{capitalized_name}!" end end class CapitalizeFormatter def format(name) name.upcase end end
  59. 1 - Classical / Detroit Style TDD describe “Benefits of

    TDD” do context “producing quality software” do it “results in a test suite” do SayHello CapitalizedFormatter
  60. 1 - Classical / Detroit Style TDD describe “Benefits of

    TDD” do context “producing quality software” do it “results in a test suite” do “Sociable” Unit Tests SayHello CapitalizedFormatter
  61. 1 - Classical / Detroit Style TDD 2 - Mockist

    / London Style TDD describe “Benefits of TDD” do context “producing quality software” do it “results in a test suite” do
  62. 1 - Classical / Detroit Style TDD 2 - Mockist

    / London Style TDD describe “Benefits of TDD” do context “producing quality software” do it “results in a test suite” do unit collaborator collaborator
  63. 1 - Classical / Detroit Style TDD 2 - Mockist

    / London Style TDD describe “Benefits of TDD” do context “producing quality software” do it “results in a test suite” do unit collaborator collaborator unit collaborator
  64. 1 - Classical / Detroit Style TDD 2 - Mockist

    / London Style TDD describe “Benefits of TDD” do context “producing quality software” do it “results in a test suite” do unit collaborator collaborator unit collaborator unit
  65. describe SayHello do it "returns a greeting using a formatted

    name" do formatter = double("Name Formatter") allow(formatter).to receive(:format).and_return("JOHN") greeting = SayHello.call("John", formatter: formatter) expect(greeting).to eq("Hello there JOHN!") end end class SayHello def self.call(name, formatter: CapitalizeFormatter.new) capitalized_name = formatter.format(name) end end
  66. describe SayHello do it "returns a greeting using a formatted

    name" do formatter = double("Name Formatter") allow(formatter).to receive(:format).and_return("JOHN") greeting = SayHello.call("John", formatter: formatter) expect(greeting).to eq("Hello there JOHN!") end end class SayHello def self.call(name, formatter: CapitalizeFormatter.new) capitalized_name = formatter.format(name) end end
  67. describe SayHello do it "returns a greeting using a formatted

    name" do formatter = double("Name Formatter") allow(formatter).to receive(:format).and_return("JOHN") greeting = SayHello.call("John", formatter: formatter) expect(greeting).to eq("Hello there JOHN!") end end class SayHello def self.call(name, formatter: CapitalizeFormatter.new) capitalized_name = formatter.format(name) end end
  68. describe SayHello do it "returns a greeting using a formatted

    name" do formatter = double("Name Formatter") allow(formatter).to receive(:format).and_return("JOHN") greeting = SayHello.call("John", formatter: formatter) expect(greeting).to eq("Hello there JOHN!") end end class SayHello def self.call(name, formatter: CapitalizeFormatter.new) capitalized_name = formatter.format(name) end end
  69. describe SayHello do it "returns a greeting using a formatted

    name" do formatter = double("Name Formatter") allow(formatter).to receive(:format).and_return("JOHN") greeting = SayHello.call("John", formatter: formatter) expect(greeting).to eq("Hello there JOHN!") end end class SayHello def self.call(name, formatter:) formatted_name = formatter.format(name) "Hello there #{formatted_name}!" end end
  70. 1 - Classical / Detroit Style TDD describe “Benefits of

    TDD” do context “producing quality software” do it “results in a test suite” do SayHello Mock or Stub 2 - Mockist / London Style TDD
  71. 1 - Classical / Detroit Style TDD describe “Benefits of

    TDD” do context “producing quality software” do it “results in a test suite” do “Solitary” or Isolated Unit Tests SayHello Mock or Stub 2 - Mockist / London Style TDD
  72. 1 - Classical / Detroit Style TDD describe “Benefits of

    TDD” do context “producing quality software” do it “results in a test suite” do 2 - Mockist / London Style TDD use both styles
  73. $ rspec --format documentation tdd_benefits_spec.rb:55 Benefits of TDD producing quality

    software helps when writing tests first produces a test suite Finished in 10 minutes and 0.64148 seconds 2 examples, 0 failures
  74. test as little as possible to give you a certain

    level of confidence class SayHello def self.call(name, formatter: CapitalizeFormatter.new) formatted_name = formatter.format(name) "Hello there #{formatted_name}!" end end tip #1
  75. test as little as possible to give you a certain

    level of confidence class SayHello def self.call(name, formatter: CapitalizeFormatter.new) formatted_name = formatter.format(name) "Hello there #{formatted_name}!" end end class CapitalizeFormatter def format(name) name.upcase end end tip #1
  76. test as little as possible to give you a certain

    level of confidence tip #1 describe SayHello do it "allows changing the way the name is formatted to be capitalized" do expect( SayHello.call("John", formatter: CapitalizeFormatter.new) ).to eq("Hello there JOHN!") end end
  77. test as little as possible to give you a certain

    level of confidence tip #1 describe SayHello do it "allows changing the way the name is formatted to be capitalized" do expect( SayHello.call("John", formatter: CapitalizeFormatter.new) ).to eq("Hello there JOHN!") end end describe CapitalizeFormatter do it "capitalizes the name" do expect( CapitalizeFormatter.new.format("John") ).to eq("JOHN") end end
  78. test as little as possible to give you a certain

    level of confidence tip #1 describe SayHello do it "allows changing the way the name is formatted to be capitalized" do expect( SayHello.call("John", formatter: CapitalizeFormatter.new) ).to eq("Hello there JOHN!") end end describe CapitalizeFormatter do it "capitalizes the name" do expect( CapitalizeFormatter.new.format("John") ).to eq("JOHN") end end
  79. test as little as possible to give you a certain

    level of confidence tip #1 describe SayHello do it "allows changing the way the name is formatted to be capitalized" do expect( SayHello.call("John", formatter: CapitalizeFormatter.new) ).to eq("Hello there JOHN!") end end
  80. tip #2 always ask why? validate the system enable safe

    refactoring document behaviour get immediate feedback
  81. tip #2 always ask why? validate the system enable safe

    refactoring document behaviour get immediate feedback to know when you’re done
  82. the goal isn’t 100% code coverage tip #3 class SayHello

    def self.call(name) "Hello there #{name}!" end end
  83. the goal isn’t 100% code coverage tip #3 class SayHello

    def self.call(name) "Hello there #{name}!" end end describe SayHello do it “has 100% code coverage” do SayHello.call("John") end end
  84. tests follow a consistent structure tip #4 describe SayHello do

    it "returns a greeting for formatted name" do stub_formatter = double("Stub Name Formatter", format: 'j0hn') result = SayHello.call("John", formatter: stub_formatter) expect(result).to eq("Hello there j0hn!") end end
  85. tests follow a consistent structure tip #4 describe SayHello do

    it "returns a greeting for formatted name" do stub_formatter = double("Stub Name Formatter", format: 'j0hn') result = SayHello.call("John", formatter: stub_formatter) expect(result).to eq("Hello there j0hn!") end end Arrange
  86. tests follow a consistent structure tip #4 describe SayHello do

    it "returns a greeting for formatted name" do stub_formatter = double("Stub Name Formatter", format: 'j0hn') result = SayHello.call("John", formatter: stub_formatter) expect(result).to eq("Hello there j0hn!") end end Arrange Act
  87. tests follow a consistent structure tip #4 describe SayHello do

    it "returns a greeting for formatted name" do stub_formatter = double("Stub Name Formatter", format: 'j0hn') result = SayHello.call("John", formatter: stub_formatter) expect(result).to eq("Hello there j0hn!") end end Arrange Act Assert
  88. tests follow a consistent structure tip #4 describe SayHello do

    it "allows changing the way the name is formatted" do mock_formatter = double("Mock Name Formatter") expect(mock_formatter).to receive(:format).with("John").and_return("JOHN") SayHello.call("John", formatter: mock_formatter) end end
  89. tests follow a consistent structure tip #4 describe SayHello do

    it "allows changing the way the name is formatted" do mock_formatter = double("Mock Name Formatter") expect(mock_formatter).to receive(:format).with("John").and_return("JOHN") SayHello.call("John", formatter: mock_formatter) end end Arrange
  90. tests follow a consistent structure tip #4 describe SayHello do

    it "allows changing the way the name is formatted" do mock_formatter = double("Mock Name Formatter") expect(mock_formatter).to receive(:format).with("John").and_return("JOHN") SayHello.call("John", formatter: mock_formatter) end end Arrange Assert
  91. tests follow a consistent structure tip #4 describe SayHello do

    it "allows changing the way the name is formatted" do mock_formatter = double("Mock Name Formatter") expect(mock_formatter).to receive(:format).with("John").and_return("JOHN") SayHello.call("John", formatter: mock_formatter) end end Arrange Assert Act
  92. describe App do describe '.current_username' do it 'is nil by

    default' do expect(App.current_username).to be_nil end end end fix flaky tests ASAP tip #5 class LoginUser def call(username) App.current_username = username end end class App class << self attr_accessor :current_username end end describe LoginUser do describe '#call' do it 'updates the current username' do username = '[email protected]' LoginUser.new.call(username) expect(App.current_username).to eq(username) end end end
  93. class LoginUser def call(username) App.current_username = username end end describe

    LoginUser do describe '#call' do it 'updates the current username' do username = '[email protected]' LoginUser.new.call(username) expect(App.current_username).to eq(username) end end end fix flaky tests ASAP tip #5 class App class << self attr_accessor :current_username end end describe App do describe '.current_username' do it 'is nil by default' do expect(App.current_username).to be_nil end end end 1 2
  94. $ rspec spec/ .. Finished in 0.00895 seconds (files took

    0.18042 seconds to load) 2 examples, 0 failures fix flaky tests ASAP tip #5
  95. class LoginUser def call(username) App.current_username = username end end describe

    LoginUser do describe '#call' do it 'updates the current username' do username = '[email protected]' LoginUser.new.call(username) expect(App.current_username).to eq(username) end end end fix flaky tests ASAP tip #5 class App class << self attr_accessor :current_username end end describe App do describe '.current_username' do it 'is nil by default' do expect(App.current_username).to be_nil end end end 2 1
  96. $ rspec spec/ .F Failures: 1) App.current_username is nil by

    default Failure/Error: expect(App.current_username).to be_nil expected: nil got: "[email protected]" # ./tip_fix_flaky_tests_spec.rb:29:in `block (3 levels) in <top (required)>' Finished in 0.02265 seconds (files took 0.10097 seconds to load) 2 examples, 1 failure Failed examples: rspec ./tip_fix_flaky_tests_spec.rb:28 # App.current_username is nil by default fix flaky tests ASAP tip #5
  97. class LoginUser def call(username) App.current_username = username end end describe

    LoginUser do describe '#call' do it 'updates the current username' do username = '[email protected]' LoginUser.new.call(username) expect(App.current_username).to eq(username) end end end fix flaky tests ASAP tip #5 class App class << self attr_accessor :current_username end end describe App do describe '.current_username' do it 'is nil by default' do expect(App.current_username).to be_nil end end end ? ?
  98. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.new(cents / 100) account.balance = account.balance + amount_in_dollars end end
  99. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.new(cents / 100) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) expect(Dollar).to receive(:new).with(5) expect(account).to receive(:balance=).with(15) AddMoneyToAccount.new.call(account, cents: 500) end end
  100. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.new(cents / 100) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) expect(Dollar).to receive(:new).with(5) expect(account).to receive(:balance=).with(15) AddMoneyToAccount.new.call(account, cents: 500) end end
  101. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.new(cents / 100) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) expect(Dollar).to receive(:new).with(5) expect(account).to receive(:balance=).with(15) AddMoneyToAccount.new.call(account, cents: 500) end end
  102. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.new(cents / 100) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) expect(Dollar).to receive(:new).with(5) expect(account).to receive(:balance=).with(15) AddMoneyToAccount.new.call(account, cents: 500) end end
  103. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.new(cents / 100) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) expect(Dollar).to receive(:new).with(5) expect(account).to receive(:balance=).with(15) AddMoneyToAccount.new.call(account, cents: 500) end end
  104. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.from_cents(cents) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) expect(Dollar).to receive(:new).with(5) expect(account).to receive(:balance=).with(15) AddMoneyToAccount.new.call(account, cents: 500) end end
  105. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.from_cents(cents) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) expect(Dollar).to receive(:new).with(5) expect(account).to receive(:balance=).with(15) AddMoneyToAccount.new.call(account, cents: 500) end end
  106. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.new(cents / 100) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) AddMoneyToAccount.new.call(account, cents: 500) expect(account.balance).to eq(Dollars.new(15)) end end
  107. watch out for fragile tests tip #7 class AddMoneyToAccount def

    call(account, cents:) amount_in_dollars = Dollars.from_cents(cents) account.balance = account.balance + amount_in_dollars end end describe AddMoneyToAccount do it "updates the account balance with the specified amount" do account = Account.new(balance: 10) AddMoneyToAccount.new.call(account, cents: 500) expect(account.balance).to eq(Dollars.new(15)) end end
  108. describe SayHello do it "returns a greeting using a formatted

    name" do formatter = double("Name Formatter") allow(formatter).to receive(:format).and_return("JOHN") greeting = SayHello.call("John", formatter: formatter) expect(greeting).to eq("Hello there JOHN!") end end avoid over isolation tip #8
  109. class SayHello def self.call(name, formatter: CapitalizeFormatter.new) formatted_name = formatter.format(name) "Hello

    there #{formatted_name}!" end end class CapitalizeFormatter def format(name) name.upcase end end avoid over isolation tip #8
  110. class SayHello def self.call(name, formatter: CapitalizeFormatter.new) formatted_name = formatter.format(name) "Hello

    there #{formatted_name}!" end end class CapitalizeFormatter def pretty_format(name) name.upcase end end avoid over isolation tip #8
  111. describe SayHello do it "returns a greeting using a formatted

    name" do formatter = double("Name Formatter") allow(formatter).to receive(:format).and_return("JOHN") greeting = SayHello.call("John", formatter: formatter) expect(greeting).to eq("Hello there JOHN!") end end avoid over isolation tip #8
  112. describe SayHello do it "returns a greeting using a formatted

    name" do formatter = instance_double("CapitalizeFormatter") allow(formatter).to receive(:format).and_return("JOHN") greeting = SayHello.call("John", formatter: formatter) expect(greeting).to eq("Hello there JOHN!") end end avoid over isolation tip #8 use verifying doubles
  113. only mock what you own tip #9 class ChargeAccount def

    call(account, amount, description) # ... Stripe::Charge.create( amount: amount, currency: 'usd', source: account.stripe_customer_id, description: description) # ... end end
  114. only mock what you own tip #9 describe ChargeAccount do

    it "bills the account" do account = Account.new(stripe_customer_id: 'cus_123') expect(Stripe::Charge). to receive(:create). with(amount: 500, currency: 'usd', source: 'cus_123', description: 'test') ChargeAccount.new.call(account, 500, 'test') end end
  115. only mock what you own tip #9 describe ChargeAccount do

    it "bills the account" do account = Account.new(stripe_customer_id: 'cus_123') expect(Stripe::Charge). to receive(:create). with(amount: 500, currency: 'usd', source: 'cus_123', description: 'test') ChargeAccount.new.call(account, 500, 'test') end end
  116. only mock what you own tip #9 describe ChargeAccount do

    it "bills the account" do account = Account.new(stripe_customer_id: 'cus_123') mock_credit_card_gateway = double('Credit Card Gateway') expect(mock_credit_card_gateway). to receive(:charge). with(account: account, amount: 500, description: 'test') ChargeAccount.new(mock_credit_card_gateway). call(account, 500, 'test') end end
  117. TDD isn’t a magic bullet tip #10 mutation testing describe

    ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do stub_ip_service = double('Stub IP Service’, my_ip: ‘192.168.2.2') expect( ClassifyIP.call(ip_service: stub_ip_service) ).to eq('even') end end end
  118. TDD isn’t a magic bullet tip #10 mutation testing class

    ClassifyIP def self.call(ip_service = WhatIsMyIpAddress.new) public_ip = ip_service.my_ip if public_ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end
  119. TDD isn’t a magic bullet tip #10 mutation testing class

    ClassifyIP def self.call(ip_service = WhatIsMyIpAddress.new) public_ip = ip_service.my_ip if public_ip.split('.').last.to_i % 2 == 0 'even' else 'odd' end end end
  120. TDD isn’t a magic bullet tip #10 mutation testing class

    ClassifyIP def self.call(ip_service = WhatIsMyIpAddress.new) public_ip = ip_service.my_ip if public_ip.split('.').last.to_i % 2 0 'even' else 'odd' end end end !=
  121. TDD isn’t a magic bullet tip #10 property-based testing describe

    ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do property_of { i = integer; integer % 2 == 0 }.check { |i| stub_ip_service = double('Stub IP Service’, my_ip: "192.168.2.#{i}") expect( ClassifyIP.call(ip_service: stub_ip_service) ).to eq(‘even') } end end end
  122. TDD isn’t a magic bullet tip #10 property-based testing describe

    ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do property_of { i = integer; integer % 2 == 0 }.check { |i| stub_ip_service = double('Stub IP Service’, my_ip: "192.168.2.#{i}") expect( ClassifyIP.call(ip_service: stub_ip_service) ).to eq(‘even') } end end end
  123. TDD isn’t a magic bullet tip #10 property-based testing describe

    ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do property_of { i = integer; integer % 2 == 0 }.check { |i| stub_ip_service = double('Stub IP Service’, my_ip: "192.168.2.#{i}") expect( ClassifyIP.call(ip_service: stub_ip_service) ).to eq(‘even') } end end end
  124. TDD isn’t a magic bullet tip #10 property-based testing describe

    ClassifyIP do describe ".call" do it "returns 'even' when the IP is divisible by 2" do property_of { i = integer; integer % 2 == 0 }.check { |i| stub_ip_service = double('Stub IP Service’, my_ip: "192.168.2.#{i}") expect( ClassifyIP.call(ip_service: stub_ip_service) ).to eq(‘even') } end end end
  125. TDD isn’t a magic bullet tip #10 consumer-driven contract tests

    client / consumer server / producer test test test test test
  126. TDD isn’t a magic bullet tip #10 consumer-driven contract tests

    test test test test test consumer test expectations of server’s API client / consumer server / producer
  127. $ rspec --format documentation tdd_benefits_spec.rb Benefits of TDD delivering software

    faster is a productive workflow lowers maintenance costs facilitates working in teams
  128. $ rspec --format documentation tdd_benefits_spec.rb Benefits of TDD delivering software

    faster is a productive workflow lowers maintenance costs facilitates working in teams adapting to user’s changes helps produce a better design it doesn’t guarantee a good design good design follows S.O.L.I.D. principles follows 4 Rules of Simple Design favours separation of concerns
  129. $ rspec --format documentation tdd_benefits_spec.rb Benefits of TDD delivering software

    faster is a productive workflow lowers maintenance costs facilitates working in teams adapting to user’s changes helps produce a better design it doesn’t guarantee a good design good design follows S.O.L.I.D. principles follows 4 Rules of Simple Design favours separation of concerns producing quality software helps when writing tests first produces a test suite Finished in 2 minutes and 0.64148 seconds 5 examples, 0 failures
  130. $ rspec --format documentation tdd_disadvantages_spec.rb Disadvantages of TDD takes time

    to write the tests doesn’t solve every design problem doesn’t solve every testing and quality problem some things just can’t be TDDed Finished in 1 minutes and 0.64148 seconds 4 examples, 4 failures
  131. tip #1 test as little as possible to give you

    a certain level of confidence tip #2 always ask why? tip #3 the goal isn’t 100% code coverage tip #4 tests follow a consistent structure tip #6 keep your tests DAMP tip #5 fix flaky tests ASAP tip #7 watch out for fragile tests tip #8 avoid over isolation tip #9 only mock what you own tip #10 TDD isn’t a magic bullet on writing tests