Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tests for security; Test for design

Tests for security; Test for design

Many people come to the world of test driven development because of the security it provides. However, when done correctly TDD provides loads of other benefits; specifically it can help us design better software.

In this talk we'll discuss tools and techniques to drive better designs with tests. Specifically we'll talk about test isolation with mocks and stubs, test speed, designing apis, and more. Everyone will leave with a better understanding of how to utilize TDD and BDD to create not just safer code, but better code.

06f8b41980eb4c577fa40c41d5030c19?s=128

Chris Keathley

October 10, 2015
Tweet

Transcript

  1. Tests for Design Tests for Security Chris Keathley / @ChrisKeathley

    / keathley.io
  2. Who is doing TDD?

  3. Who likes doing TDD?

  4. Change

  5. Change is hard

  6. Change is inevitable

  7. Change is good

  8. Change is our responsibility

  9. Design

  10. Design is hard

  11. Design must be flexible

  12. Design for purpose

  13. Tests

  14. I love tests

  15. Zen

  16. Tests provide guard rails.

  17. Focus

  18. Empower Refactoring

  19. “Refactoring without tests is just moving crap around”

  20. “Refactoring without tests is just moving crap around” - me

    (just now)
  21. Protection from Regression

  22. Protip: Managers love the idea of security

  23. There are bugs in your code.

  24. All of this is good

  25. But is isn’t everything

  26. Test Driven Design

  27. Lets talk about…

  28. Lets talk about… TDD 101

  29. Lets talk about… TDD 101 Commands and Queries

  30. Lets talk about… TDD 101 Commands and Queries Designing Apis

  31. Lets talk about… TDD 101 Commands and Queries Designing Apis

    Test Speed
  32. A note for those who are feeling pedantic

  33. “Any sufficiently complicated test suite contains an ad hoc, informally-

    specified, bug-ridden, slow implementation of half of Erlang” - also me (also just now)
  34. TDD 101

  35. Methodology

  36. Methodology Write a failing test

  37. Methodology Write a failing test Make that test pass

  38. Methodology Write a failing test Make that test pass Refactor

  39. Methodology Write a failing test Make that test pass Refactor

  40. describe Platypus do it "can drink" do end end

  41. describe Platypus do it "can drink" do expect(platypus.stomach).to include coffee

    end end
  42. describe Platypus do it "can drink" do platypus.drink(coffee) expect(platypus.stomach).to include

    coffee end end
  43. describe Platypus do it "can drink" do platypus = Platypus.new

    coffee = Coffee.new(grams: 400) platypus.drink(coffee) expect(platypus.stomach).to include coffee end end
  44. We know too much implementation

  45. describe Platypus do it "can drink" do platypus = Platypus.new

    coffee = Coffee.new(grams: 400) platypus.drink(coffee) expect(platypus.stomach).to include coffee end end Exposes Data
  46. describe Platypus do it "can drink" do platypus = Platypus.new

    coffee = Coffee.new(grams: 400) platypus.drink(coffee) expect(platypus.drank?(coffee)).to eql(true) end end
  47. class Platypus def initialize @stomach = [] end def drink(coffee)

    @stomach << coffee end def drank?(coffee) @stomach.include?(coffee) end end
  48. Rely on interfaces and hide state

  49. But is this the right interface?

  50. describe Platypus do describe "when it drinks coffee" do it

    "becomes energized" do end end end
  51. describe Platypus do describe "when it drinks coffee" do it

    "becomes energized" do expect(platypus.energized?).to eql(true) end end end
  52. describe Platypus do describe "when it drinks coffee" do it

    "becomes energized" do expect(platypus.energized?).to eql(true) end end end
  53. describe Platypus do describe "when it drinks coffee" do it

    "becomes energized" do platypus = Platypus.new expect(platypus.energized?).to eql(true) end end end
  54. describe Platypus do describe "when it drinks coffee" do it

    "becomes energized" do platypus = Platypus.new platypus.drink(Coffee.new(grams: 400)) expect(platypus.energized?).to eql(true) end end end
  55. class Platypus def initialize @stomach = [] end def drink(coffee)

    @stomach << coffee end def energized? @stomach.any? end end
  56. Think about your interfaces

  57. Test your boundaries

  58. Commands & Queries

  59. OO is about messages

  60. Object

  61. Object Queries

  62. Object Queries Commands

  63. Queries Commands Return things Change things

  64. describe Platypus do describe "when it drinks coffee" do it

    "becomes energized" do platypus = Platypus.new platypus.drink(Coffee.new(grams: 400)) expect(platypus.energized?).to eql(true) end end end
  65. describe Platypus do describe "when it drinks coffee" do it

    "becomes energized" do platypus = Platypus.new platypus.drink(Coffee.new(grams: 400)) expect(platypus.energized?).to eql(true) end end end Query
  66. describe Platypus do describe "when it drinks coffee" do it

    "becomes energized" do platypus = Platypus.new platypus.drink(Coffee.new(grams: 400)) expect(platypus.energized?).to eql(true) end end end Command Query
  67. Designing Apis - Queries

  68. describe Platypus do describe "when it drinks coffee" do it

    "gains energy" do end end end
  69. describe Platypus do describe "when it drinks coffee" do it

    "gains energy" do expect(platypus.energy).to eql(45) end end end
  70. describe Platypus do describe "when it drinks coffee" do it

    "gains energy" do platypus = Platypus.new expect(platypus.energy).to eql(45) end end end
  71. describe Platypus do describe "when it drinks coffee" do it

    "gains energy" do platypus = Platypus.new platypus.drink(coffee) expect(platypus.energy).to eql(45) end end end
  72. describe Platypus do describe "when it drinks coffee" do it

    "gains energy" do platypus = Platypus.new coffee = double(energy: 50) platypus.drink(coffee) expect(platypus.energy).to eql(45) end end end
  73. describe Platypus do describe "when it drinks coffee" do it

    "gains energy" do platypus = Platypus.new coffee = double(energy: 50) platypus.drink(coffee) expect(platypus.energy).to eql(45) end end end Assert the result
  74. class Platypus def initialize @stomach = [] end def drink(coffee)

    @stomach << coffee end def energy @stomach .map { |item| item.energy } .reduce { |acc, i| acc + i } end end
  75. class Platypus def initialize @stomach = [] end def drink(coffee)

    @stomach << coffee end def energy @stomach.map(&:energy).reduce(:+) * 0.90 end end
  76. Design collaborating interfaces

  77. Interfaces MUST be enforced

  78. describe Coffee do describe "#energy" do it "calculates the energy

    per gram" do coffee = Coffee.new(grams: 400) expect(coffee.energy).to eql(115) end end end
  79. class Coffee def initialize(grams:) @grams = grams end def energy

    ((@grams / 17.42) * 5).round end end
  80. Keep interfaces flexible

  81. class Bacon def initialize(grams:) @grams = grams end def energy

    9001 end end
  82. Queries Assert only the reponse.

  83. Queries Don’t expect external calls

  84. Queries Provide doubles if the interface is unknown or takes

    too long to build.
  85. Designing Apis - Commands

  86. describe Platypus do describe "when it waddles" do it "wears

    out its shoes" do end end end
  87. describe Platypus do describe "when it waddles" do it "wears

    out its shoes" do expect(shoes).to receive(:wear) end end end
  88. describe Platypus do describe "when it waddles" do it "wears

    out its shoes" do shoes = Shoes.new expect(shoes).to receive(:wear) end end end
  89. describe Platypus do describe "when it waddles" do it "wears

    out its shoes" do shoes = Shoes.new expect(shoes).to receive(:wear) platypus.waddle! end end end
  90. describe Platypus do describe "when it waddles" do it "wears

    out its shoes" do shoes = Shoes.new platypus = Platypus.new(shoes: shoes) expect(shoes).to receive(:wear) platypus.waddle! end end end
  91. describe Platypus do describe "when it waddles" do it "wears

    out its shoes" do shoes = Shoes.new platypus = Platypus.new(shoes: shoes) expect(shoes).to receive(:wear) platypus.waddle! end end end Outgoing Command
  92. class Platypus def initialize(shoes:) @shoes = shoes @stomach = []

    end def waddle! @shoes.wear end end
  93. class Platypus def initialize(shoes:) @shoes = shoes @stomach = []

    end def waddle! @shoes.wear end end Dependency Injection
  94. describe Platypus do describe "when it waddles" do it "wears

    out its shoes" do shoes = Shoes.new platypus = Platypus.new(shoes: shoes) expect(shoes).to receive(:wear) platypus.waddle! expect(platypus.shod?).to eql(false) end end end
  95. class Platypus def initialize(shoes:) @shoes = shoes @stomach = []

    end def waddle! @shoes.wear end def shod? @shoes.good_condition? end end
  96. class Shoes def initialize @worn = false end def wear

    @word = true end def good_condition? !@worn end end
  97. Expect outgoing commands to be sent

  98. Commands Assert all side effects

  99. Commands Inject dependencies

  100. Commands Mock outgoing calls

  101. Test Speed

  102. Fast tests are important

  103. TDD requires feedback

  104. Isolate from other dependencies

  105. Test at the lowest possible level

  106. Write fewer tests!

  107. Conclusion

  108. Test Interfaces

  109. Test once

  110. Test everything

  111. Test for design

  112. Security will follow

  113. Prepare for change

  114. Thanks! Chris Keathley / @ChrisKeathley / keathley.io