 Hey, Rubyhack, how are we doing? Oh, I have to remember the camera's only on this side. So, oh gosh, I haven't spoken in a few years, so it might be a little rusty, this'll be kind of fun. Howdy, I'm Dave Rady, I've done a few podcasts that you might recognize, maybe, I don't know. I did Ruby Rogues for about five years, and then picked off Greater Than Code, and worked with them for a while, had a lot of fun. I blog sporadically at whydayfly.com, which kind of matches my sense of humor. I'm Dee Brady at Twitter. I work at Cover My Meds. If you wanna come help me fix the healthcare system in America, which is broken, and get patients the medications they need with, by shortening the time to treatment, by getting rid of the red tape, please come talk to me, we are hiring engineers. New folks, we do have an intern co-op program, but it is currently full for this year. But definitely do give me a holler if you are interested, even new folks, I'm happy to help network. What I wanna talk to you today about is science. We had really bad testing pain in some of our library, well, some of our applications, where it was really hard for us to modify some of our code, or to implement new features, or to make any progress. And I started, I thought, what the heck, I'll just do science. I'll just come up with an idea, we'll try it, we'll see how it works, we'll gather results. And over the course of the year, my findings were primarily that I was wrong about a lot of things, about the ways to do code well, and ways to do testing well. I did find some rules of thumb, I'm gonna share those with you today. This slide, I'm gonna say I'm a lot, I am out of practice. Just did it again. Awesome. The third thing that I found is that Sandy Metz is right and I'm wrong, even if I don't like it. And the fourth thing is, okay, a drawing about behavior versus implementation. This'll be my own thing. I'm gonna do my talk kind of an agile style. I'm gonna give you my entire talk right now in the next like four slides, and then if you wanna leave, you can. But we'll talk about these four things. Actually, the first three. The first thing I wanna talk about is rules of thumb. These are the ideas that we came up with over the past year. The thing about a rule of thumb, or sorry, the rule of thumb of rules of thumb is that they're right about three out of four times, two out of three times, I don't know. You just make up the number. It's more than 50%. They're right more often than not, but not always. So feel free to take them with a grain of thought. Ooh, salt, salt, I like thought better. Take them with a grain of thought. Think about them. And if you don't like them, maybe think about the fact that they're right more often than not before you just dismiss them wholesale. So consider trying some of these things. This is, credit is due for this drawing to Sandy Metz, or at least the ideas behind it, the crappy drawing is mine. Sandy gives three rules for what to test. In an auditorium system, we talk about message passing between objects and systems. And there are three kinds of messages. If we're testing class A here, A can have three messages basically. It can receive a message from something else. It can send a message out, and it can send a message to itself. This is basically the three messages that can happen. And Sandy's rules of thumb, if you will, are that if somebody sends a message in, you want to test, the way you test that is you send the message, or yeah, you send the message to A, and then you assert that the correct behavior happens. That A does the right thing. If A is supposed to send a message to B, however, you stop it, and you merely assert that the correct message was sent. This decouples A from B. I did a lot of code this year where we went ahead and let things that were testing A go ahead and run code in B. This is a bad time. And then the last one, this was so hard for me. If A sends a message to yourself, don't test it. If it sends it to itself, don't test it. That's A's business. That's A's implementation. That's A's details. If you write a test to test that, you are coupling your test to your implementation. If you want to change that implementation, you will have a broken test that does not affect the behavior of A. You're wasting your time in the future. So these are my own notes for that, basically. Short version is I was wrong, Sandy was right. I found these last two especially very challenging. If you take one thing from my talk today, it is this. If you are testing a thing, test the behavior and only the behavior of the thing and only the thing that you are testing. I want to point out that I use the word thing, not object, because A and B could be applications. They could be hardware devices. We could have from Ajah's talk this morning for making change, you could have A be a cash register and B could be one of those little mechanical things that spits the coin automatically into the cup. They could be hardware devices. We don't know. This is gonna be really important as we go forward in this talk because the scale that you move up and down in your application affects what kind of specs you write. So when you do like unit testing or unit specking, A is gonna be a class or an object, but when you start doing your entire application, the same rules apply. When you start writing your request specs or your controller level specs, your application level rather, you do the same thing. You send a message to your application, you assert the behavior. If your application in a service oriented architecture wants to go talk to another service, you stop it and say, no, I'm not testing you right now. I'll go up a level and do an integration test of the entire system. That's fine, but not at the time. I wanna lead in this diagram. It looks way more complicated than it is. You don't have to worry about it. I made this up. This is just an idea for communicating in this talk, something that I discovered over the past year. Basically, as you work through code every day, you're somewhere on this map. If you know what the object is supposed to do, if you know what the behavior is, you're on the top half, and if you know how the object is supposed to work, you're on left, thank you. You're on the left half, am I right? And this is gonna be key because if you are in, I'm gonna be talking about these quadrants to try and use shorthand. So I apologize in advance for that. So let's beat the horse a little tiny bit here. We work in all four quadrants every single day. Quadrant four is kind of like the terror zone. Like it's the exploration and wonder. It's like somebody says, let's write an app that uses this API. Cool, what does the API do? I don't know, what do you want your app to do? I don't know, okay, we're in quadrant four. Unknown behavior, unknown implementation, let's do this. We'll have fun. All right, most of us spend a lot of time in quadrant three. That's legacy code. You have a known implementation, but you don't know how it behaves, right? And by legacy code, I can mean stuff who wrote 30 seconds ago. If you wrote the code and you went right test, and you wrote the code and you write the test, right? You let the code get ahead of the test. You're in quadrant three. You're doing known implementation, unknown behavior. Test driven development, which I love, is up in quadrant two, okay? That's known behavior, unknown implementation. I loved, I'm James. I am running on adrenaline right now, and I literally could not tell you my own name right now. Thank you, James. I love James' talk because he mentioned the fact that, yeah, if you specify the behavior, you don't even need to know the implementation. Just go for it, okay? And what we do every day at work is we start in quadrant four and we try to work our way up to quadrant one where we have tested code that behaves and does the thing that it's supposed to do. We have known implementation and known behavior. That's where we kinda wanna get to be. There's two abbreviations that I'm gonna use over and over today. TDD, which most of you've heard, which is test driven development. The other one I'm gonna use is TAD, which for lack of a better thing is just something I made up. Test after development, okay? It just means that the code came first and then the test, right? So if you are in quadrant four and you break something down into, you go write some code, you're doing test after. You're in the TAD quadrant. Hi, Tad. That's not a happy quadrant, but you know. Anyway, if you write your specs first, you end up pushing up through quadrant two. But the goal is still to get from quadrant four to quadrant one. Does that kinda make sense? Everybody kinda clear on that. I'll try to not just say, oh, quadrant two, we'll do this. I'll try not to get too cryptic with it. These are my notes here. Oh, the other important thing is that natural cycles do exist whichever way you try to do your development. If you try to spend your time in quadrant three, natural development cycles will occur. And some of these have unique phases or perhaps symptoms that can help you identify which cycle you're in. TDD has certain phases that just don't happen in TAD and Tad has certain phases that don't happen in TDD. So did you know you had phases, Tad? So. We know, we know. Right, we know. Our worker is coming in. We see his phases every day. Yes, all right. So are we ready? Should we talk about some lies about some truths about testing? So that is my entire talk. You can go now if you want. But let's get into some lies that we tell ourselves, some assumptions that I had challenged. The first one is I love testing. That's a lie. We speak about testing exclusively in the language of pain. My tests are too slow. I can't get anything done. My tests are too messy. I can't write a new feature. My tests are too brittle, too flaky, too fragile, too low value. They're making me crazy. I hate my tests. I mean, I love testing. I think it's great, all right? We'll go to a conference and somebody will say, your tests should be high value. They should be fast. They should be clean. That's great, right? That's positive language. But you don't want, there's no positive value in a fast test. The reason you want a fast test is because a low test sucks, all right? Okay, so think about that. Have you ever said this? In a few days, it's great and all. But I got to ship this. Anybody have ever pulled out, I've heard this at work. I've heard this come out of my mouth. This is a phasor symptom that is unique to the TAD cycle. So if you hear this come out of your mouth, trigger warning, you're in quadrant three. You're down in known implementation, unknown behavior. And there are going to be some symptoms and other phases and some things that will happen to you as a result of being in that cycle, okay? If you hear yourself, oh yeah, so the prevailing force of the TAD cycle is urgency, right? We've got to ship this or the server's down or we've got a breach or we've got something, right? There are this, all right, I've stopped saying um, but now I can't complete sentences. Neat. Um, back to um, ah, first talking four years. Gonna be fun. So, uh, this, if you're finding yourself in this cycle, you will find this urgency will place countervailing force, that's the opposite of a prevailing force, a headwind on refactoring, on TDD, on going back and cleaning up your code. And I want to point out that sometimes this is entirely appropriate, okay? If the server is down at three o'clock in the morning, the only thing I want is to go back to bed. Writing a test, a regression test to catch whatever knocked the server down is future Dave's problem. At eight in the morning, that Dave can solve that problem and write a test. That's totally appropriate. If you've got a data breach, you want to close the breach. Don't spend an extra three days doing a test for a day, right? Urgency is a valid reason to go into known implementation, get the door locked, get the server back up, get the cash flow restarted. Anybody said this one, um, testing is hard. Unfortunately, this one's true. This one has turned out to be true pretty much across the board. The good news is it's a skill. It's the kind of skill that you can get better at. And this is really calling back to the quadrants diagram. We are not taught to program with testing, right? We're taught to just sit down and write some code. 10, print hello world, 20, go to 10. The test, the moment you know the behavior is when you type run and hit enter and you get a screen full of code. Now you know the behavior, right? You get taught to go through quadrant three. Here's another big plug. You get taught to think this way when you're, from little. If you don't know how to solve a problem, what do you do? Trial and error? Um, ask mommy. Ask mommy, okay. Break it down, right? We're taught to just break it down. Break it down into smaller pieces that you can manage. Break it down into manageable pieces. This is literally an algorithm or a heuristic for getting to known bits of implementation. And that is literally going to push you very strong into quadrant three type thingy. This, this, it's not like Stephen Covey, quadrant two time management. Um, which is a great book by the way. But anyway, it will push you down into quadrant three where you're gonna end up writing your implementation. You're gonna figure stuff out. What started the impetus for this talk is my coworker John who's actually here today. He and I paired on something. We'd been given a story card, unknown behavior, unknown implementation. And John, I was, this was about six months ago. So I was about halfway through these experiments with testing and John said, I'm really glad we're pairing on this because I know how I want to write this but I have no idea how we're gonna test it. And I said, oh, that's interesting. Because I know exactly what I want to test first. I have no idea how we're gonna make it work. How did two engineers with relatively equivalent skillsets end up painted into the corner but opposite corners of the same room? Okay. John painted himself into quadrant three. I painted myself into quadrant two. And the reason why this happened is because the way you get yourself painted into a corner is by picking which the corner you paint yourself into is the direction you take your first step in. So that will come back up. Testing reduces costs. This is absolutely true but not for you. Most testing rewards are long-term return on investment. Regression bugs, catching regression, stopping known defects, that sort of thing. Some of the experiments that I did this year were trying to tighten the testing loop to try and get testing to pay off faster like using guard to automatically catch my stuff, using CI, most of it should be using CI. I tried some experiments to see if I could make testing pay off right now. I can't say that I've got rock solid findings in that direction. I will say it's promising. We did find some improved quality right off of that, much like pair programming costs more, literally costs you an extra programmer, but you produce better quality code the first time through. Testing upfront with TDD produced better quality code. We also recently had a thing where we completely avoided a rat hole and I love rat holes. I love them. I love just, hey look, side effects. Let's go this way. And TDD actually stopped me from doing that recently. So that's kind of fun. Having 100% code coverage. I guess all I really have to say with this one is test coverage is fantastic. You should use it, but you should only use it as a tool. Don't ever try to get to a specific number of code coverage, unless you have none, then yeah, absolutely, it's fine to go into your CI machine and say, let's say that we're gonna reject this until we're at like 80%, it's not like that. But I will assert to you, and this is my opinion, take it or leave it at your own peril, either way, it is actively harmful to gamify code coverage. If you make it your goal, you are going to start rewarding optimization and optimization is trying to get the biggest bump in that number for the lowest amount of input or of effort and shortcuts end up meaning we're gonna couple our tests to our implementation, we're not gonna refactor our tests. We're just gonna write the test and just slap it out there and we're gonna get, oh, there's a trigger word, diminishing returns. Have you ever heard of this? Code coverage is great, but after like 92%, you start getting diminishing returns. It's not worth it to try to get to 100, you can't test for everything, right? Okay, you're in a quarter and three cycle. You are in this thing. Now, I really hope my slides back up that going through T2 doesn't put that way. Before I show you this next slide, I wanna point out that it was very painful for me to write this next slide. Our specs advanced features are awesome. No, they're not. And even the mini test lovers in the house. Yeah, I love how fast mini test is. I don't like anything else about it. I've tried, I've tried so hard. I've tried so, so hard. And I do like mini test and I like a lot of things about it, but I love our spec. I love its expressivity. I love how readable it is to me because I've been using it for so long. It's what I'm fluent in. It's my native testing language, I guess. But using things like shared examples and shared context and include context and things where you share an example that in the shared examples, it will include a context that will inject less into your existing stuff. Oh boy. Okay, we had some code like this. And all this really did was bend your test framework to support bad code. And that's bad because now when you go to clean up the code, you have tests that will fight you because they are going to assert that your bad code should continue to exist and should continue to function poorly. So, yeah, why would you do this, you monster? This was the hardest one for me. This was the hardest set of findings. I will assert this. This is what I, the second point on this was a huge surprise for me. If you simplify your tests, or don't, sorry, don't write a big framework or a big DSL in your framework. Just go make your code easier to test. Your tests will clean themselves up and you will end up with much cleaner RSpec as a result or Minitest. I do like, that's one thing I do like about Minitest is that it makes simplicity a prevailing force because it is so simple out of the box. If you wanna build something really big, it's going to punish you and I think that's a good thing. And Minitest, or I'm sorry, RSpec, which I, again, I love, Myron Marston, if you're watching this, I apologize for digging on RSpec. But it will, it can encourage you to get a little sloppy when you shouldn't. TDD makes simplicity a prevailing force. This is an important concept. If you write a test on the behavior first, then hard to test code doesn't get to exist because you've got a test that you have to make pass. You've written a piece of code and you've got an instance variable and it's set up by a class method that's by a loaded thing and you're like, how am I gonna load this? How am I gonna test this, right? TDD makes that go away because that hard to test code doesn't get a chance to even get off the starting blocks. It makes the simplicity of your test suite a prevailing force. It pushes you to keep things simple. This was an interesting discovery. Test first equals test first. No it isn't. When we think through a problem, we think about, okay, I'm in quadrant four, I need to, I've got unknown implementation and behavior, how do I fix this? I know, I'll break this down, I'll do this, this, this. Okay, cool, I've got the implementation in my head. Now I'm gonna go write my test first, like a good little engineer. That's not test first, that's testing after I thought up the implementation, but before I wrote it, I somehow I think tight tootie b-b-wee isn't going to catch on. But, you know, hashtag it along with Ruby hack 2018. We'll see. Be aware of that, that you can lie to yourself that I'm doing test first, I'm doing test first on it, therefore I'm doing test driven development. You might not be. You might be letting, if you're writing very close testing cycles, you will be minimizing some of these negative effects of writing hard to test code because if you write just a tiny bit of code and then you have to go test it, you don't get to go very far. You don't get to make a big bunch of mistakes before you have to go back and clean your stuff up. But you're still a little bit behind the curve. You're still at a point where you can write some code and you can say, I'm done. And now I'm under pressure to say, I'm done and I need to leave or I need to ship or I need to back out. Again, sometimes appropriate, but as a general skill, it's not TDD to do it this way. Let's talk about breaking things down to use TDD. If you want to break things down into behavior, it's pretty simple. What you do is you stop asking, how do I make this work? Start asking, what do I want it to do first? When Azure was talking about making change about the counting out the coins, right? There's this balance and there's this recursive algorithm that we can use. What if we had just had two objects? What do you have, a cash register and the coin ejector? Okay, how do I break that down? How are we gonna make these talk, you know what? I don't know. I genuinely don't know. I'm stuck. So what I'm gonna do is I'm gonna go up a level and I'm gonna say, if I ring up 95 cents and the customer tenders $1, there should be five cents change made. There should be a nickel in the change drawer. That's behavior. You've gone up a level and now the entire interaction of your system, whether it's recursive or prology or whether it's enlist, doesn't matter. You've gone up a level, you've specified behavior. Now you can start dropping back down in and go, okay, all right, let's maybe have the coin counting thing have methods that we can send. We can send like an eject penny or eject nickel or eject dime, eject quarter, right? Now you can start writing behavioral specs that if I need to make change for seven cents, I should expect that eject nickel should we get called once and eject penny should get called twice, right? Okay, that's behavior. We've dropped down a level, so the behavior is looking a little more implementationy, but it's still just a conversation. We don't know how the coin emitter is going to eject the coin, okay? Could be a voltage on a solenoid wire or it could just be an emulator. We don't know. The trick, again, on my drawing, which is useless except for as a tool for discuss this, is that you want to try and push yourself up into the quadrant two. The reason we don't like to go there, this is not in my slides, but I'm gonna go for your range here for just a second. The reason we don't end up in Q2 is you have to give up knowing that you can complete the problem, because we love that as engineers, don't we? We like to basically say, I wanna solve this entire thing. I don't wanna start working on this. Has anybody started a program, gotten halfway through and then discovered you couldn't finish the program? Like when I started out a program, this happened to be all the time. I've written things that, you know, it turns out that JavaScript didn't run in the web browser that fast in 1994. You know, I was gonna write a video game. Didn't have enough CPU power. Okay, in test driven, you don't know, but it's not as bad as you might think. How many first timers do we have for Rubyhack? We've got a few hands. Okay, every single one of you used test driven development, behavior first, to get to this amphitheater. You pulled in, you found parking. You did not know how to get into this amphitheater, but you were able to take a step closer towards the building, confidently knowing that in the future I'll be able to solve this problem. You didn't even have to start, sit in the car and go, oh God, once I get inside, how am I gonna pick a seat? Right? You don't need that implementation. There are things that we do every day in life where we don't know the implementation, but we boldly go forward. Code often is not one of them, but it can be. And that is kind of the secret trick to my talk is to try pushing towards behavior more. I guess it's not that secret, is it, it's on every freaking slide. Okay, I guess I'm not that clever. Tad, this is your cycle. The code leads, the test follows the implementation. Tests are under pressure now to bend to the implementation. Your test suite will become slow, ugly, and brittle. The benefit of this is that testing starts easy. It becomes hard later, and that's because this is a debt-driven cycle. Debt, like technical debt, but like actual debt where you just like, hey, we'll just charge it. Not a problem, we'll just charge it, we'll just charge it. This is the same kind of thing. Write a quick and easy test. Now you should be doing red, green, what? Refactor. Do you always do it? No, because red, green, ship it is what really kind of happens a lot more than maybe it should. And we ended up with this big refactoring because we left the little refactoring alone for way too long. TDD, the test leads and the code follows. There is pressure on the code to not be hard to test. The test is already there and says you work for me now. Make it work. I don't care how you implement it, but when I send you this message, I expect this behavior and now you're gonna bend the code to follow it. Your test suite stays, I really wanted to write clean and make like a complete judgment call there, but we're all humans, right? The reality is it stays clean error and it will clean up a messy code base, but yeah, we're humans. I will say that testing starts harder with TDD, especially if it's not your go-to skill. It's harder to do testing in general and it's harder to do TDD than it is to do TAD and that's another reason that the testing after your development is so appealing. But yeah, it's an investment driven cycle and that's what ends with an investment is it pays off over time and I do have data on that, which I'm kind of excited. This is less of a lie or a truth, but I just started writing questions down at this point. Is TDD always the best thing? I really want to say yes, but TAD beats TDD hands down cold in three cases. The first one is when you're already in quadrant three, you've been given an existing code base or known implementation or you know the code base cold because you've written 17 of them over the past 30 years. Testing after the fact is gonna beat TDD. It's gonna be far more efficient. You're already optimized for known implementation. Unless you're willing to put in the effort to back out of knowing your implementation and asserting behavior, TAD is gonna be better. It's gonna be faster and I wish I could say TDD was the one true way with the reality is there's a trade-off there and sometimes you just gotta get stuff shipped. Sometimes customers actually want you to deliver things. So we already talked about urgency. Urgency trumps everything, right? TAD is better than TDD in terms of getting something out quick. You know what's even quicker than that? No testing. When the server's on fire at three o'clock in the morning, I just want the server to stop being on fire so that I can go back to bed. Now I am gonna test it first thing in the morning after I've had some more sleep. But right now at three o'clock in the morning, urgency trumps everything. And then again, question three, this is a little more dangerous question than you think. Do you trust yourself to finish? We often do not. This is, I'm genuinely asking, I'm not challenging. I'm not saying you can't finish. I'm saying, do you trust yourself to finish? With TDD, you write some of the behavior and then you trust yourself to finish the next step, to finish the next piece, to finish the next piece. You end up with TDD always working on the right thing, on the most important thing, but you don't know if it will work until you're finished. That's a little terrifying. With TAD, you know it works from the first line of code because you've worked out the whole thing in your mind. And if it's a really big thing that you've worked out and it takes you a long time to write it and the customer pivots halfway through, you will successfully build a working wrong thing three months down the line. I've had that happen. But if you fear your ability to finish something, maybe working some implementation out upfront is probably okay. I'm gonna recommend you not use TDD when this, okay, urgency, servers on fire, you have an active intrusion or breach, cash flow is stopped. If you cannot change the code, not even to write an adapter layer to put behavior around somebody else's API, TDD is just gonna bring you pain and tears and make you angry at who wrote that code. Implementation, again, very well understood. If you're an expert and you've done seven of these, you're gonna be more efficient unless you value the exercise of saying, I know how to do this procedurally or I know how to do this without tests or I know how to do this, you know, my old fashioned way. Do I value spending 10 times more effort on this than I actually need in order to learn other skills like TDD skills? There are times when circumstances preclude learning. Again, urgency, anybody ever had your boss tell you, I don't pay you to learn? Okay, we'll talk about that. Your boss is wrong. Your boss absolutely pays you to learning and you as a developer absolutely have an onus to learn every day. That is your job is to learn. You literally have one job and that's to learn. I've talked about the diagonals this way. What about the other way? If you're up in quadrant one, you got known implementation and known behavior, does it matter? Either one works, right? When you're down in quadrant four, neither one works. So does it matter? I don't know, I don't have firm data on this. I will assert my belief that when you're in quadrant one, either one works. So you can learn the new habits and patterns of behavior. When you're in quadrant four, you're at C, you're lost. And the habits and patterns that you have established define your available options. So if you want to be able to get lost at C and use TDD to find your way home, practice it when you already know the implementation and you already know the behavior. Practice it when things are calm and you can do. This was my favorite discovery of the year. You can't test exploratory code, right? Oh, you can't test what you don't know how to write. Wrong, you absolutely can't. You can't write a program you don't know how to write except that your boss makes you do this every day. Do you still think it's not your job to learn? Still think you don't get paid to learn things? You can always describe a next behavior. If you can't describe a behavior, a next behavior, like you could start a first behavior for the coin counting thing of if I have exact change, I expect nothing to come out of the change till, right? That's a pretty easy, simple behavior. You can implement that real easy, do nothing, right? Now let's say, now let's the next behavior, right? Customer gave me one penny more than, you know, rang up 99 cents, customer gave me a dollar. I should get one penny. I can probably figure out how to write that code, okay? If you can't figure out the next behavior, go up a level and figure out what the behavior of the system above you is. All right, home stretch, guys. Let's tie this all together. Refactoring. We're supposed to do it, we should do it, and we don't, right? The reality is we're human. And especially when you're in a test after development, there is pressure on you to not polish the rivets, to not refactor. You'll get diminishing returns if you refactor forever, okay? One of the things that we tend to do, this was a discovery that I made this year, is that we as Ruby programmers tend to refactor our Ruby code into structure. We will break, we'll take a really coherent idea that makes obvious sense, and we will break it apart into an enumerator and an interface grabber and a scheduling manager. And now that idea that was really clear has been spread across three files and it's broken and it's lost. Katrina Owen gave a really good talk. I feel really bad that I didn't go get the link and put it on the slide. But if you look, search for her, she gives a really good talk talking about refactoring Ruby. She's done a bunch of talks about refactoring Ruby and you should watch all of them because she is smarter than me at refactoring Ruby. She will tell you to refactor your Ruby code to ideas and then you will have very expressive code. Ironically, you will end up with code that's easier to test. Or at least the tests will read like they're describing the real behavior of a system rather than like a CS math problem, right? When there's 13 bits set on the POSIX register, then this thing should eject 3.2 times, all right? That doesn't describe a behavior of anything that I would expect. But if you refactor your code to ideas, you are going to have tested and follow this better. If you refactor two ideas, you wanna use principles like dry. Don't repeat yourself, right? This is the idea that every piece of knowledge should have a single place to live in your code. Principles like dry, the solid principles, these protect you from change. They're good ideas. You absolutely should do them in your application code. Do they always apply to test code though? This was another really hard one for me. Ruby tests tend to de-factor themselves into following your implementation. The way you refactor your tests is by pushing them towards behavior. Don't worry about the structure. Don't worry about, I mean, if your code is following ideas, great. But don't worry so much about implementation in your test. Push your test towards behavior. And principles like wet, which is the opposite of dry. It stands for write everything twice. Wet lets you use a test to tell the story. Wouldn't it be a lousy book if you opened the book and the entire story was there is a beginning, there is a middle, there is an end, the end. That's a lousy story. But we write our specs and our tests so many times to be that way. For example, I added the second line after I just talked this morning. If I've got a library that can add two numbers, this test, does this test tell you anything? I mean, you can see that it should add two things together, right? I can add argument one and argument two together. And then that should equal some. But can you tell just from looking at that code if my code works? Like if this test passes, do you know if the code works? Right, what if arg one is three and arg two is negative 4.2 and some is the word pants? If that passes, there's something very wrong with your code. Okay, the coin emitter, the coin counter, if we say, I expect the bank account for the coins for amount to equal coins. Okay, there's a story here. I've got a thing that says there exists a story. But that's a lousy story. Let's look at a better story. I expect the library to add three and four and get seven. Oh no, I've got magic numbers. I'm using the spec three, three, four places. I've got the three and the four hard coded in a whole bunch of places. Oh no, I've got duplication. That's terrible, right? Except that the problem is that you can look at that first line. Three, three, four, add, you get seven. If you can read that line of code, if that line of code is green in your test output, do you believe that the library function adds correctly? Right? Okay. And then if you change the values in the variables, the second line there is literally the same test if you're using variables. But if you're telling the story up front, you can add the word pig to the word truck and get the word pig truck. I don't know why you want that word, but if you do, that's cool. Okay. There's a little bit of implementation detail here on the bank coins thing, but if I say give me the coins for a seven, I needed the tiniest implementation so that it would fit on a slide. So I decided to use an array of pairs. The first thing in each pair is the denomination and the second number in each pair is the number of those coins. So I expect the change for seven to be one nickel, five, one, and two pennies, one comma two, right? So we can look at this. If you know that your implementation happens to be, and again, at the level of making change, it's okay to know that implementation because you are testing that particular message. If you go up a level, this might not be an appropriate level of implementation to know about. But isn't duplication bad? I struggled with this one so hard. I still have coworkers I cannot convince that this is not the worst thing ever to do. But we have, in our JIRA boards, we have over 10 story cards that have been marked and annotated as taking an order of magnitude longer because the tests were so painful. We had written so many clever things, a shared example that would include a context for you and would inject let's into your spec and you're like, where the hell did this variable come from? I didn't include anything. Yes, you did. When you said it behaves like this, you also didn't include context. You didn't do it. It did it to you. And it was awful. And you have this, we had this little bit of code. We had one card. Yeah, we had the shortest card took two days for a one line code change. We needed one change, tiny little thing. Went in and said, yeah, when this happened, we needed to do this. And there was a block of about 17 tests and 10 of them passed and seven of them failed. So we had to figure out, shared example include context on this file. Okay, no, that's in the support directory. Okay, this is over here. All right, we finally found it. All right, very clean. One line of code asserts this thing. So we flipped it to be the other way around. 10 tests fail, seven tests pass. Took us a day and a half to realize we were never gonna win and we just ripped the whole damn thing out and took us about two hours to rewrite it using wet. Now the problem with wet is that it will cause the shotgun surgery anti-pattern. Anybody familiar with that? It's a little bit older school terminology. Shotgun surgery is when you need to make a change to your application and it requires that you make the same, when you shoot a shotgun, there's like pellets that come out and hit in like seven places, right? You have to make matching changes in like seven places. If you go through and you have all of your examples are adding pig and truck together to give you pig truck and then somebody comes along and says, no, we are only gonna add capitalized strings. You're gonna have to change all of those specs. You really are. In the past 12 months, this has happened twice. Both times took five minutes to fix. The worst one was 35 changes in seven files. I will take 10 minutes of my life over two freaking days for one of these cards, okay? I still can't convince some of my devs that this is not the literal actual anti-Christ and I'm not sure I don't believe it myself. But I've got some data that's really challenging me. This is a rule of thumb that you might wanna squint at and think about the idea of having your tests, hard code values, yeah, keep them really, really simple and tell the story. This makes it so that when your maintainer sits down and opens your code, they can read the story and they go, oh, well, if it does this, then I know the code works and I know how to use my code. So in the last few minutes here, let's talk briefly about ways that you could practice. How are you, like so you decided, maybe I've convinced you that TDD is great and you need to get better at it. So how do you practice not knowing implementation? How do you practice that, okay? Turns out one of the easiest ways to do it is to go find a bunch of new problems. Katrina Owen runs exorcism.io. They're just programming challenges. Pick a language, she's got like 4,700 languages now that she supports. Pick a language, write some code, she'll give you a problem, you solve the problem, your peers review it and you learn more and you iterate and it's absolutely fantastic. You can use this to practice, give it, get a new problem each time you do it, okay? Project Euler is a lot of fun. They're usually math problems. Well, they are math problems. They're really freaking hard math problems and that's great. Those are a little tougher to TDD because you have to figure out how to solve the problem. It's a math problem that you have to solve so behavior is kind of simple, okay? All right, I mean, like Fibonacci, I can probably figure out how to write the behavior for that but I mean, when you're trying to figure out the Ackerman number of function of something, well, okay, the implementation is more detailed. So exorcism is probably better but I really want to talk about coderetreat.org. Has anybody heard of this? Global Day of Code Retreat, okay? Go do this, it's free. Go sign up for it and go to one. It is absolutely fantastic. You write the game of Life Conway's Game of Life, you have little plants that appear and die and then make little cool things go flying across. You write that, you spend 30 minutes writing it, then you throw away your code, you change the requirements and you start over and then you 30 minutes later you throw it away and you change the requirements and you start over and each time you start over you have a new implementation requirement and you don't know how to do it and you have to start over at behavior. It's a fantastic way to learn this. My next slide is titled How to Unknown a Thing? Sometimes we wish we could unsee something. I can't give you that, but I can tell you how to unknown a thing and that's take something you've already written, some little ding-bat project that you like, something that's completely up in Q1 and do the Global Day of Code Retreat thing, rip out the implementation and then stipulate a new requirement. We're gonna make change for coins but we are not gonna use recursion. You're not allowed to use recursion. Do it some other way. Oh crap, okay? Yeah, you might use multiplication, you might write a for loop, we're gonna have to figure some way of doing it, right? Start over. Now you've got an opportunity to start with the behavior. The interesting thing that you will notice is you will probably not actually test the stipulation except possibly if you introduce new objects. If we were to say in Ajax's example there's a cash register and there's a coin ejector. If there's a third class kind of living in the middle that's actually like the coin counter that we discover that that kind of flows out, well we're gonna need unit tests for that. We will be testing part of the stipulation there. But at the higher level, we know how to make change. Those tests won't change. You'll change your stipulations but those won't do it. Can you throw TDD at a big legacy Rails app? You absolutely can. This was the essence of my experiments for this entire year. TDD always works, it's just not always the best approach. The main thing I'll say about this is it does take effort and yeah, good Tad beats, good TDD, especially in quadrant three. The one thing I will say is that top-down development and test-driven development, it will surprise you. It will surprise you. Can you use it to fix a bad test suite? Absolutely. How you do it, get your coverage metrics, don't worry about the number, just get them. Find out if you've got enough coverage to do it safely. Then, wet it down. Unroll everything, decouple the A to B cause, refactor your test to demand behavior from your code, make the code follow the behavior and you may not end up changing the code very much cause you may not have time but you can use this to ease a lot of the pain in your code base. And then one day this happened. One line change, two minutes and it just worked and I kid you not, we spent the next half hour freaking out going what did we miss? The test suite is supposed to punish us now and it doesn't. What have we missed? We have to have missed something. This has happened now dozens of times. We spend a little less time in step three each time. We still kind of go, did we miss something? Right? Okay. And we have started trusting our tests. The last little splinter I wanna stick in your brain is a chat from Noel Rappen as I was turning these ideas over in my head and he basically said when your code and your test disagree, do you trust your test or do you trust your code? I cannot honestly say I usually trust my tests and that's because I'm spending way too much time down in the known implementation, unknown behavior. The tests are the unknown and so I don't trust them. I am out of time. Thank you very much and happy Ruby hacking.