$30 off During Our Annual Pro Sale. View Details »

Your TDD Treasure Map

Your TDD Treasure Map

We know testing is vital and makes refactoring painless. But how to set sail to that TDD treasure? Yarr, we need to test to get experience, but need experience to test. Let’s draw a map with simple strategies for identifying test cases and building a robust test suite. X marks the spot w/ TDD tools for newbies and seasoned pirates alike.

Ajina Slater

May 25, 2022
Tweet

More Decks by Ajina Slater

Other Decks in Programming

Transcript

  1. doodlingdev Aji they/them (all pr onoun s welcome) Hi! I

    mean.. ahoy! I'm Aji (hard J) I've been a professional developer since 2016, but if you count Geocities, I've been putting code on the web since 1996. Anyone in the room not yet born in 1996? I'm very excited you're here but now wish I had not asked that
  2. doodlingdev introductions I never know what to say in these

    intros.. so how about.. when I was in 4th grade I got the lead in the school play
  3. doodlingdev "Of Mice and Mozart" where we told the life

    story of Wolfgang Amadeus Mozart through the eyes of a family of mice that lived in his walls I played Mozart at 9 and I've been trying to relive that glory ever since
  4. doodlingdev let's begin I'm going to take a guess that

    we're on the same page about something, I'm going
  5. doodlingdev the benefits of tests (briefly) So I'm going to

    skip the arguments that try and convince you of the bene fi ts of robust automated tests that
  6. doodlingdev ( From Five Factor Testing By Sarah Mei )

    madeintandem.com/blog/five-factor-testing/ And although I didn't understand the degree at the time, that was certainly the feeling they tried to instill in us at my coding bootcamp
  7. doodlingdev Please sir, I’d like some more tests.. but we

    had so little time, and I graduated never having written a test, without feeling the bene fi ts of a good test suite...
  8. doodlingdev plus It seemed like a paradox. Write the thing

    that veri fi es the code I will have written BEFORE I am going to have written the code I will have been about to write.
  9. doodlingdev PFFFT okay, let me just jump into my time

    machine to take a look at the PR I'm going to put up tomorrow.
  10. doodlingdev Testing FIRST is di ff i cult. True for

    seasoned sailors, but even more so for those newer to this career when it's a challenge still to even reason about what code to write at all.
  11. doodlingdev How was I going to make it out of

    this mess? Like Archimedes in the bathtub or Newton and Gravity, we need a compelling yet apocryphal story of discovery to go with it.
  12. doodlingdev So.. scratching my head over this problem I wandered

    over to my bookshelf that has all my jobby job career type books.
  13. doodlingdev and Atul Gawande's The Checklist Manifesto, wonderful tomes that

    have set me on the path when I needed them before...
  14. doodlingdev wait.. this is Robert Lewis Stevenson's Treasure Island. what?

    How did this end up on this shelf? so embarassing, the child of a librarian, misshelving happening in my own home.. But it dawned on me, of course it's here! because...
  15. doodlingdev But think about it, a treasure map IS a

    fl owchart. Sure, one of the core stengths of a fl owchart is the branching, the logic, being able to map out decisions that lead to di ff erent outcomes but Stevenson's captain Flint only wants the happy path that ends in dubloons
  16. doodlingdev 🗺 ❓ 💰 ⚔ 🐊 WEST means eaten by

    crocodiles, and just like that the whole crew is 410 gone
  17. doodlingdev 🗺 ❓ 💰 ⚔ 🐊 Not only is a

    fl owchart useful for retracing your steps to your long-buried ill-begotten gains, but one of the most helpful artifacts for understanding the control fl ow of a system, invaluable documentation for new team members.
  18. doodlingdev A kind of visual pseudocode.. Think about how many

    low-code and no-code solutions are built with fl owcharts as a UI, because they're so simple to understand but potentially powerful.
  19. doodlingdev we can leverage that same strength as a shared

    language to build understanding between technical and non-technical team members and stakeholders and get buy-in from "the business" in a way you never can with code.
  20. doodlingdev But no longer will the usefulness of this tool

    stop at the code's edge, we're going to learn how to directly apply it to writing tests and eventually code...
  21. doodlingdev (pirate voice) So consider yerselves hornswaggled, ensnared inta' being

    my crew aboard the good ship Capybara, en route to the promised land where testin' before ye' leave harbor can be smooth saillin'. Before his half hour be over, we'll raid the ship o'knowledge and you'll have new techniques o' yer own te' boot
  22. doodlingdev I apologize for that accent to everyone watching who

    is a 17th century Carribean pirate, sorry.. privateer
  23. doodlingdev the process A chart can relate fairly directly to

    code, serving to mimic the control fl ow of an app.. but how it relates to testing might not be as obvious. We'll use a pretty common situation of a Rails controller action to demonstrate how we can take a handful of AC's to a fl owchart and turn that into a test rigging, test code, and application code.
  24. doodlingdev feature when I say feature, i mean that to

    be any unit of work.. be it a whole feature, a bug fi x, partial implementation, whatever. I'll stick to saying "feature" for brevity.
  25. doodlingdev rigging There's probably an actual term for it, but

    when I'm talking about a test suite's "rigging" i mean the describe, context and it blocks that make up the tests.
  26. doodlingdev Rspec.describe PiratesController d o describe 'pirate/id' do context 'when

    logged in' d o it 'shows the pirate page' do like here, these lines with the descriptive strings, and the levels of indentation that show how they relate to one another, that's what I'm calling the "rigging"
  27. doodlingdev rspec although this process will work with any testing

    library... ruby or not, my examples are going to be in rspec, because as we all know it IS a pirate's
  28. doodlingdev The app our team is building is Pirate Cove,

    where freelance freebooters can list their available skills and raiding resume for captains looking to round out a crew for upcoming misdeeds.. sorry.. adventures
  29. doodlingdev Pirates Users our app has users, we call them

    pirates, and pirates have pro fi le pages, o ff the shelf rails resource routing with the standard CRUD actions
  30. doodlingdev Pirates piratecove.app/pirates/:id our app has users, we call them

    pirates, and pirates have pro fi le pages, o ff the shelf rails resource routing with the standard CRUD actions
  31. doodlingdev Pirates Create, Read, Update, Destroy piratecove.app/pirates/:id our app has

    users, we call them pirates, and pirates have pro fi le pages, o ff the shelf rails resource routing with the standard CRUD actions
  32. doodlingdev crew mob programming This afternoon we're pairing, all of

    us, I'll drive we've picked up a ticket with a new user story
  33. doodlingdev As a pirate, I should be able to fill

    out the form on my profile page and submit it to change my information and it looks like it is to implement one of those CRUD actions, As a pirate, I should be able to fi ll out the form on my pro fi le page and submit it to change my information.
  34. doodlingdev Acceptance Criteria (ACs): 1. User can update a pirate's

    information from the profile page (read slides) There are two eventual acceptance criteria or requirements on this ticket, but we're going to start with just the fi rst and pick up the second as we go.
  35. doodlingdev Begin with the action we know is going to

    take place, typically a user acting on the system here: Our pirate user submitting form data.
  36. doodlingdev Standard fl owchart symbols have start and end points

    as rounded shapes, We'll start with that core action at the top center of our workspace
  37. doodlingdev User submits form data when that gets submitted, an

    action occurs on the backend, we're going to save that new data
  38. doodlingdev User submits form data DB Save and how do

    we resolve? show the updated information with a redirect to :show
  39. doodlingdev User submits form data DB Save Display profile this

    alone could be the happy path of our feature sure it's gonna get a little more complex but at least it's enough to write the matching rigging
  40. doodlingdev User submits form data DB Save Display profile Begins

    with a user action, some backend work, and feedback for the user. Beginning middle end
  41. doodlingdev User submits form data DB Save Display profile #

    frozen_string_literal: true RSpec.describe PiratesController, type: :request do describe “patch#update” do (pause) at the top of an rspec fi le we get something like this to start, but everything we're doing will be under patch#update, this is the last time we'll see this to save the slides getting too cluttered
  42. doodlingdev User submits form data DB Save Display profile right

    away I can see two outcomes that we expect will happen after form data is submitted.
  43. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” by rails convention: redirecting to the pirate show page and the only way the pirate show page is going to have the proper information
  44. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” is if we also update the db to re fl ect changed data
  45. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” This doesn't mean that every bubble in the fl owchart needs to be tested, and we'll see that coming up. In this case, we're testing more than just the very end, but also any action that changes something *outside* of our current test subject. A side e ff ect.
  46. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Our test subject is the PiratesController, and this makes a change in the database, way outside the controller. That's a result that we want to guarantee is happening (to the best of our ability) so we'll protect it with a test.
  47. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” That way, if anyone ever comes along and makes a change that a ff ects that outcome
  48. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” ⁉ even if the redirect still happens
  49. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” 💬 thank you. we'll have feedback that an expected result has been a ff ected. That's our tests preventing regressions.
  50. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” (pause) The other piece to our test rigging are context blocks. The it blocks are going to match up to the END results, the e ff ects of the action
  51. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” the context will correlate to the setups that exist, those are found by following each individual path through the chart
  52. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” pretty easy at the moment, right now we've only got one path through the fl owchart
  53. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” it could make sense to wrap these two it blocks in a context block
  54. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” context “happy path” do end but i don't think we actually gain anything by doing that, it's the only context that exists, and it's already wrapped by the "patch update" describe block. Personal preference, but I'd leave it o ff for now.
  55. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” context “happy path” do end What else this indicates by being only one context, is that our setup for these two tests will be the same.
  56. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” 👋 Each individual path through the chart will have a di ff erent state when the action is performed. We're focused down to a level where there is only one action, and most of the time you’re building out tests, that's
  57. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” how it’s going to be. Maybe di ff erent granularities, sometimes di ff erent params, but one single insighting action.
  58. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” The only way to take different paths through the chart is with a different beginning state or different params of the action In the ideal state when you’re focued on testing one action, the only way to take di ff erent paths through the chart is with a di ff erent beginning state, or di ff erent params of the action.
  59. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” so we're still here, we kinda know that it's not going to stay this simple And this is a position you might fi nd yourself in often. You know there's more looming, but it hasn't actually shown up yet.
  60. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” let's not complicate it, I'd rather start moving forward on something I'm sure of, naive or not, rather than speculate and end up down an irrelevant rabbit hole.
  61. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” We've got some ambiguity, but we've also got something concrete, so let's move forward on that, I'm con fi dent enough that what comes next will reveal itself as we go through this exersize or that if it doesn't...
  62. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” it could be outside the scope of what we're trying to accomplish right now, plus I want to err on the side of shipping software over analysis paralysis
  63. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” and really, this is the whole happy path, right? nothing goes wrong, this is essentially what needs to happen.
  64. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” So if we write these tests, we'll have a north star to guide us as we sail along and seas get choppier. If ever we make changes, and THESE tests fail, we should do some rethinking.
  65. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Yup, our new tests will be preventing OUR regressions. Just another reminder that "the next developer" who will work with code we've written... can be ourselves, even just an hour later.
  66. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” So, should we write some tests?
  67. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” given when then the action the setup the result Wait, before that, I'm also not here to introduce or debate the di ff erent methodologies for writing individual tests, that's another talk, or workshop. And they mostly all boil down to some variation of GIVEN, WHEN, THEN.
  68. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” it “redirects to pirate :show” do end what might test code for this portion of our rigging look like?
  69. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end let's start with the setup, we know we need a pirate whose information is going to be updated, and whose ID will be part of the url, based on rails' restful conventions.
  70. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end and now the action, we already said, that's going to be when the user submits the form data, so if we use rspec's request syntax, it might look something like this
  71. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) and eventually our expectation, that it should redirect
  72. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end Here we are with test code for both it blocks from before. Redirects to the user show page and updates the database.
  73. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end Now we’ve got some wind in our sails! We’ve understood our fl owchart as a context and still at just the one path, our GIVEN is the matching setup. (Pause)
  74. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end the action that begins our fl owchart has become the WHEN: the action taken in our test cases.
  75. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end We’ve identi fi ed the two end states, one for the http response, and one for the database
  76. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end And we’ve captured them as our THEN in expectations
  77. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end (pause) As I'm building out a system, I'm trying to uncover moments where we might be taking something for granted. An assumption that we've built on top of that isn't as guaranteed as we're treating it
  78. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end So I'm asking myself, 'what here is _built on a lie_?' what here could go wrong? and my eye is drawn to this.
  79. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end These params are sent in by a user. You know users, and so do I...and I don't trust 'em. They will fi nd ..interesting ways to stress a system. Brand new ways to use our software that we never dreeeeamed of.
  80. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) it “redirects to pirate :show” do end expect do patch(pirate_url(test_pirate), params: { name: “Captain J. Flint”, position: “Captain” }) end .to redirect_to(pirate_url(pirate)) it “updates db to reflect changed data” do test_pirate = Pirate.create({ name: "Billy Bones", position: "First Mate" }) patch(pirate_path(test_pirate), params: { name: “William Bones”, position: “Captain” }) expect(Pirate.find(test_pirate.id).name) .to eq "William Bones" end In reality, that's a gift, it really is and I believe that. But right now.. it's a problem. So far our tests are assuming everything is going to go smoothly. We need to handle what happens when patch params don't pass validation.
  81. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Let's fold up the test code, like we're in our editor, and add this params protection. What would params that don't pass validation change about the fl ow of the chart?
  82. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Yesss! That change will never make it to the database. And because that can fail, we need to add to the fl owchart, before the persistence, a check for validity.
  83. doodlingdev User submits form data it “redirects to pirate :show”

    it “updates the db with changed data” Valid? DB Save Display profile We notate this fork in the road with a diamond Because in Flowchartland, which is a province of Diagramopolous, a diamond represents a decision. Something in the setup or the action is a ff ecting the outcome
  84. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no the value of which decides the branch of the fl ow to continue down, and now we truly have more than one path through the feature, and with it another context that needs testing.
  85. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no let's trace the two paths so we can see what we're dealing with
  86. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no if a path is a context, we're able to tell that one context is separate from the other. the nodes in the fl owchart that are re fl ected in the rigging as our ...
  87. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no in the rigging as our ...existing specs cannot be reached by the new path, they are an entirely di ff erent context
  88. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no let's re fl ect that in the rigging. (pause) But how can we know what the context involves, it's not as simple as an endpoint.. there isn't one node in the fl owchart that equates to context, but many along a path.
  89. doodlingdev it “redirects to pirate :show” it “updates the db

    with changed data” User submits form data DB Save Display profile Valid? Show form with errors yes no Let's ask ourselves a question, what given, what setup or input will cause the decision to go to the right where we see the two it blocks we already have?
  90. doodlingdev it “redirects to pirate :show” it “updates the db

    with changed data” context "with valid params" do end User submits form data DB Save Display profile Valid? Show form with errors yes no valid params, right?
  91. doodlingdev it “redirects to pirate :show” it “updates the db

    with changed data” context "with valid params" do end context "with invalid params" do end User submits form data DB Save Display profile Valid? Show form with errors yes no and then the opposite, invalid params, causes the fl ow to the left.
  92. doodlingdev it “redirects to pirate :show” it “updates the db

    with changed data” context "with valid params" do end context "with invalid params" do end it "renders :edit" User submits form data DB Save Display profile Valid? Show form with errors yes no and the it block, based on rails conventions we'll render the edit page again and send the object over to the view with those validation errors attached
  93. doodlingdev it “redirects to pirate :show” it “updates the db

    with changed data” context "with valid params" do end context "with invalid params" do end it "renders :edit" User submits form data DB Save Display profile Valid? Show form with errors yes no Let's act on these tests and fl owchart that we have currently. what do we want to do next? Personally.. I'm not going to feel good about these tests unless I see them fail. We haven't written any code for them yet, so if they pass, there is something fi shy going on.
  94. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no it “redirects to pirate :show” it “updates the db with changed data” context "with valid params" do end context "with invalid params" do end it "renders :edit" Finished in 0.0819 seconds (files took 5.08 seconds to load) 3 examples, 3 failures Great news! the tests failed. That means we can write some code to make them pass.
  95. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no it “redirects to pirate :show” it “updates the db with changed data” context "with valid params" do end context "with invalid params" do end it "renders :edit" def update end We're going to keep this as simple as possible, but let's think about the code that we'll need in order to follow the fl owchart and satisfy the tests.
  96. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no it “redirects to pirate :show” it “updates the db with changed data” context "with valid params" do end context "with invalid params" do end it "renders :edit" def update end @pirate = Pirate.find(params[:id]) we'll need to know which pirate
  97. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no it “redirects to pirate :show” it “updates the db with changed data” context "with valid params" do end context "with invalid params" do end it "renders :edit" def update end @pirate = Pirate.find(params[:id]) if @pirate.update(pirate_params) redirect_to pirate_url(@pirate) if the update works, we'll redirect
  98. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no it “redirects to pirate :show” it “updates the db with changed data” context "with valid params" do end context "with invalid params" do end it "renders :edit" def update end @pirate = Pirate.find(params[:id]) if @pirate.update(pirate_params) redirect_to pirate_url(@pirate) else render :edit end and if not, render edit. I didn't do anything fancy here, this is direct from the rails generators, condensed for slide readability. how do our tests fare?
  99. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no it “redirects to pirate :show” it “updates the db with changed data” context "with valid params" do end context "with invalid params" do end it "renders :edit" Finished in 0.1219 seconds (files took 5.08 seconds to load) 3 examples, 0 failures bingo! great work every body.
  100. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" Let's take just a moment to re fl ect on how we got here, we used the ticket requirements to make a simple fl owchart that met the user story, the happy path
  101. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" and from that fl owchart came our test rigging
  102. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" We wrote some tests and uncovered a failure state
  103. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" which led us to expand on our fl owchart
  104. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" and rigging
  105. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" From there we used our understanding of the system at play to write the application code
  106. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" chart <!>, rigging <!>, test <!>, chart <!>, rigging <!>, code <!>
  107. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" All that is to say, this process isn't going to be linear most of the time.
  108. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" Usually we won't have written the whole entire fl owchart with everything in scope, then move on to the entire rigging, then the test code, then the app code.
  109. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" We'll bounce around, acting on the next bit of information that we're con fi dent about, uncovering assumptions as we go, and adding to our framework as we progress.
  110. doodlingdev User submits form data DB Save Display profile it

    “redirects to pirate :show” it “updates the db with changed data” Valid? Show form with errors yes no context "with valid params" do end context "with invalid params" do end it "renders :edit" well.. we don't really have everything that's in scope, do we? remember, there's that second acceptance criteria.
  111. doodlingdev Acceptance Criteria: 1. Can update a pirate's profile info

    2. Only authorized pirates can perform updates 1 we already have, can update a pirate's pro fi le info, but 2 here says that only authorized pirates can perform updates.
  112. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end so before our check for validity, in the same way that we added the valid? step before persistence
  113. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end we have a new step that can interrupt the process before reaching the end states we had charted before
  114. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end which leads to another path through the chart. ignore for now the implementation of even having a logged in user, I've only got ( x ) more minutes with you.
  115. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end another path should make us think what? another context
  116. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end using the technique we did before, we can wrap the expectations based on when their paths diverge. The fi rst time any paths diverge, they go in two di ff erent directions. Green to the left, blue and orange to the right.
  117. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end That tells us there will be two contexts, and it's the decision where they branch that tells us what the context is.
  118. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end Authorized, no: that's green.
  119. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end and because the other paths share the state of a pirate that has permissions to change this data
  120. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end the context about being authorized will wrap them both.
  121. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end But we've done this kind of exercise before.. why bring it up again? because I want to throw a metaphorical match in our powder keg.
  122. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end Turns out what makes someone authorized... isn't so cut and dry. Authorized yes/no is perfectly fi ne for the fl owchart here... because at the controller level, that's the fl ow of this action.
  123. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end but to be authorized.. the logged in pirate is EITHER the pirate being changed OR a pirate with the admin role
  124. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end Same Pirate? no yes no yes Admin? what was once 3 paths through the chart is now fi ve, if we re fl ect that in the test cases we go from 4
  125. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no 401 Same Pirate? no yes no yes Admin? context "logged in user is the same user" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user is admin" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user not admin or user" do it “sends 401” end to 7. that's 57% more tests.. and I like tests and all, but conceptually this doesn't feel too much di ff erent than what we already had
  126. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no 401 Same Pirate? no yes no yes Admin? context "logged in user is the same user" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user is admin" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user not admin or user" do it “sends 401” end We added this section, but it makes the same decision: authorized yes or no.. it's just a more complicated process. Maybe you're already standin' where i'm leading you.. or maybe you're on the way..
  127. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no 401 context "logged in user is the same user" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user is admin" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user not admin or user" do it “sends 401” end Same Pirate? Admin? no yes no yes if this box was opaque...
  128. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no 401 Same Pirate? no yes no yes Admin? context "logged in user is the same user" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user is admin" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user not admin or user" do it “sends 401” end Authorized? and all we saw was authorized yes/no.. the rest of the paths would have the same information and the same fl ow, right?
  129. doodlingdev context "logged in user is the same user" do

    context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user is admin" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user not admin or user" do it “sends 401” end User submits form data DB Save Display profile Valid? Show form with errors yes no 401 Same Pirate? no yes no yes Admin? Authorized? Does the PiratesController need to know how to tell if a pirate can make the change? or does it just need to know whether to make 'em walk the plank or not? the latter, right?
  130. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no 401 context "logged in user is the same user" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user is admin" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user not admin or user" do it “sends 401” end Same Pirate? Same Pirate? no yes no yes Authorized? Call with pirate and editing pirate Same Pirate? Admin? no yes no yes Unauthorized Authorized so let's encapsulate that logic in an object. Lookit this, robert lewis stevenson and standy metz, back together after all this time.
  131. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no 401 context "logged in user is the same user" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user is admin" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user not admin or user" do it “sends 401” end Same Pirate? Same Pirate? no yes no yes Authorized? Call with pirate and editing pirate Same Pirate? Admin? no yes no yes Unauthorized Authorized and because we can test the authorization logic over in this object's tests... (some might even call it a service object) it doesn't have to be a part of our controller test at all.
  132. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 User submits form data DB Save Display profile Valid? Show form with errors yes no 401 context "logged in user is the same user" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user is admin" do context “with valid params” do it “redirects to user show page” it “updates the db to reflect changed data” end context “with invalid params” do it “sends error message” end end context "logged in user not admin or user" do it “sends 401” end Same Pirate? Same Pirate? no yes no yes Authorized? Call with pirate and editing pirate Same Pirate? Admin? no yes no yes Unauthorized Authorized We can mock the call to the service object, within the test.. force it to say yes or no.. all part of our setup and suddenly we're back!
  133. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end with just authorized yes/no. And the next time you're looking to encapsulate some logic away in a service object...
  134. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end look for parts of your fl owchart where those paths go apart, do something self contained, but then converge back together. like we did here.
  135. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end but if we think about it, we already have similar nodes in this fl owchart, don't we?
  136. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end yes! right here! this isn't a single thing.. it's active record taking our params hash, doing --something-- with Arel, spitting out SQL, traversing to a database (a whole di ff erent program), updating a resource.. imagine the fl owchart on that!
  137. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end and Active Record just handles it all so we can put a single box in our fl owchart.
  138. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end In the same way that we zoomed IN to get to an authorization service class, and we understand the zooming behind the persist node, we can also zoom out from our controller.
  139. doodlingdev User submits form data DB Save Display profile Valid?

    Show form with errors yes no Authorized? yes no 401 context "with valid params" do it "redirects to pirate :show" it "updates the db with changed data" end context "with invalid params" do it "renders :edit" end context "pirate is unauthorized" do it "returns 401: Unauthorized" end context "pirate is authorized" do end Up until this point we've been looking at a single action...and everything else happens without our in fl uence.
  140. doodlingdev this is a fl owchart for ordering a book

    from amazon. this is very high level. really zoomed out, all the way to the user. evvvverything is abstracted away, they're not thinking about html, servers, databases, none of it. and yet this is an incredibly useful diagram, even for testing, even for writing code
  141. doodlingdev in the same way that our happy path tests

    were our north star while building and refactoring, something like this can be the north star for an entire business or development team, able to ask themselves "can a user still order a book?" or "does this help a user order a book?". probably not amazon anymore because nowhere on here do I see "shoot founder into space". I don't see "let him come back" either but we did.
  142. doodlingdev each of these purple blocks is an action by

    a user. sign in.. add to cart.. each and every one of them could be a controller action, each with it's own fl owchart as intricate (or moreso) than the one we just built. but at this level, this "can a user order a book" level, those aren't important, for the same reason that it wasn't important to our controller if the user was an admin or a speci fi c pirate.
  143. doodlingdev still each branch or each fl ow is going

    to need a test, maybe at this level a context maybe not, again.. personal preference. but i see at least..5? 6? tests here? eventually every purple box reached by the path of a test from "start" to "end"
  144. doodlingdev i hope that helps you see that these aren't

    "rules" on how to do the Treasure Map "technique", but some ideas and advice on how you might be able to apply it in your work. I literally do this in my work.
  145. doodlingdev This is a commit message I wrote just last

    week. That's right.. I put the fl owchart IN TO The commit message. And if you too want your commit messages to bring all the boys to the yard
  146. doodlingdev the tool I used to make this plaintext fl

    owchart is ascii fl ow at ascii fl ow.com. So yeah, take these ideas, use 'em, throw away what isn't working, add on where it falls short.
  147. doodlingdev OH and if you DO add on or do

    something cool with it, please please tell me, on the twits, the gram, github, any of the things.
  148. doodlingdev I would love to know how you remix this...

    or adapt it to other frameworks and languages because
  149. doodlingdev Arr, We might be here under the auspices of

    land lubbin' RAILS rather than ocean waves, I can without OBJECTIVE-C whether yer features be crafted o' RUBY, EMERALD, carved of ELM, adorned with JADE and PERL or crusted in RUST, arrrrrrrR, this ELIXIR fl ies truthy as a DART for all who sail the C from JAVA to CEYLON. In LUA more formal errrrr..LANG-uage, and you might consider it BASIC, but even through a COMMON LISP I wont be hearin' any SMALLTALK, be'cuz this ship be SWIFT as an OCAML crossing the mighty GO ...bi desert. But to give this here GROOVY ASSEMBLY some UNITY and CLOJURE, let me be CRYSTAL clear with ye, ALGOL to Davy Jones' Locker and BASH a hundred foot PYTHON before I give up me Test Driven Development. And now that you've seen the AMPL SCAL-A this technique, I hope yer acceptance criteria ever be clear, yer test suite not scuttle yer deployment, and the cloud never take the wind out of yer sails, so that the good ship CAPYBARA can see you home.