Learning Test-Driven Development

#test-driven development #software testing #testing in production #javascript #pyhton #tdd #go #Golang

You may think test-driven development wouldn't work in your preferred programming language, or that it would disrupt your code writing — this Book Club episode proves otherwise. Saleem Siddiqui, author of “Learning Test-Driven Development,” and Dave Farley, author of "Modern Software Engineering," review the multiple ways test-driven development can yield more effective results and produce higher quality code. 

This episode is sponsored by Harness

Harness is the only end-to-end platform for complete software delivery. It provides a simple, safe and secure way for engineering and DevOps teams to release applications into production. Try Harness for free.

Intro

Dave Farley: Hi, my name is Dave Farley. I'm a software developer, author, and a big fan of test-driven development. I'm here today talking to Saleem Siddiqui, who's written "Learning Test-Driven Development," which is a smashing book, which I've got here. If you're beginning out with test-driven development, it's a great book. If you've tried it, and you're not quite sure, it's a great book. And so, we're going to be talking about that here today. Saleem, welcome to the GOTO Book Club.

Saleem Siddiqui: Thanks, Dave, for the kind words. I'm going to co-opt, frankly, shamelessly steal some of your introduction. I'm also a practitioner, and if it wasn't a dead giveaway by the book, I've written also an avid practitioner and a firm proponent of test-driven development. Dave and I share, I guess, some part of our history together, having worked for ThoughtWorks in both of our pasts, where we learned, I hope, some of the skills and philosophies that bind us together. I was really happy when this opportunity to talk to you, Dave, came kind of to my doorstep, and of course, in the context of something we're both passionate about. So, I look forward to talking to our audience, and with you on the subject.

Test-driven development applied in 3 languages

Dave Farley: So, one of the interesting things...well, there are many interesting things about your book, but one of the interesting things about your book is that you chose to write it in three different languages. So, who's the audience? Why did you do that, and who are you aiming this at?

Saleem Siddiqui: Yeah, so good question. One of the things that I have heard, and it's a fair criticism or at least a complaint against adopting TDD in practice, is oh, it doesn't work in my stack, it doesn't work in my language. I understand that. I have a bit of empathy for that. And the reason being Kent Beck, who with all deference, calls himself the rediscoverer of test-driven development, not the inventor, he didn't just rediscover test-driven development or write the seminal book on it, he also created JUnit. And of course, that has had its history, its popularity, but it's also given people, developers, well-meaning developers but mistaken ones, I would say, the ability to use that as an excuse. Oh, it only works in those kinds of languages, or in the JVM, at most, it doesn't work in my world. I want to take that head-on by saying, look, there are three different languages. Of course, you could argue that JavaScript and Python have some similarities, but Go is better and frankly, by its own admission, is not really TDD. So, there is a contrast between the three languages which I do address directly in the preface as well. So, that was one reason.

And to your question, who is it targeted at, it's targeted at people like myself, frankly, developers, people I work with. As I mentioned in the preface, a really large impetus towards writing this was my own personal experience over the last several years, seeing developers across the spectrum of their experience struggling with adopting TDD in practice, developers who are otherwise, frankly, more capable than I am, but really couldn't apply TDD proverbially "out of a paper bag." And I felt that this was not only a sad state of affairs, frankly, for the industry but also something of a learning gap that they could overcome. So, I wrote it for all of those people. If anybody finds something of value, I think the effort would not have gone in vain.

Dave Farley: That's an interesting thing. I often get a similar kind of pushback when helping people to adopt my preferred approach to software development, which is continuous delivery, which includes test-driven development, of course. But I often get the pushback, oh, it couldn't work in my circumstances with this technology, or whatever else. I've yet to find a technology or a circumstance where either one is not applicable, as a fair bit of TDD with stuff built writing firmware devices for various kinds of hardware, one way or another. One of my colleagues wrote a unit version for Unix shell scripts, which we were doing infrastructure as code, and we were testing the infrastructure as code scripts using those. We were even mocking out some of the infrastructures as code pieces during the TDD. So, I think it's interesting. One of the things that I always also found interesting was just comparing and contrasting the differences in approach through the three different languages, as I was looking through the book.

Saleem Siddiqui: It's great that you mentioned that, about having done it in different contexts. I make a passing reference to having used COBOL unit in my own past. I would not normally associate COBOL as a language that TDD could work in, but it does. I used it, I demonstrated it. And I would invite people to go back to Ken Beck's book's preface, where he talks about having learned test-driven development from punchcard developers of yore. So, if punchcard developers in, I don't know, some bygone decade were using it, then I have some empathy, but I would say not a lot of empathy or understanding for the excuse or the argument that it doesn't work in my stack. If it works in a punchcard, it will work for you.

Dave Farley: Yes. So for my book, "Modern Software Engineering," I did some research on the history of software development and software engineering, and one of the seminal things in software engineering was a conference that was held in 1968, which was hosted by NATO, to for the first time, to try and define what engineering for software meant. In the proceeds, one of the things that I read was a treaty by Alan Perlis, a famous computer scientist of the day, absolutely describing test-driven development. In languages like ALGOL and COBOL, and you know, Fortran and those sorts of things, and, on punchcard systems. 1968. So, I think Kent is right, that that's where it comes from. Kent is modest when he talks about, reinventing it, but it's partly true. Certainly, the ideas were around before then. I think Kent Beck wrote JUnit on an airplane ride, I read somewhere.

Saleem Siddiqui: It's interesting, some of these stories of genesis. It;'s great, I mean, actually, not that we want to take opposite sides on this to people who practice it or not, but if it was to be a debate or a confrontation, I would say the traditionalist position is it is traditional. Like, if you're not doing this, you're kind of bucking the trend. I mean, there's nothing that's more traditional than NATO and DARPA and all of those things. If you want to think of big government agencies saying this is the way software should be developed, then I guess that's pretty much the mainstream.

Dave Farley: So, I also read some sort of stuff recently about the flight control systems for the Mercury program, which predated the Apollo missions, were written using test-driven development in NASA. Until NASA got all bureaucratic about their software sometime later, which people like Margaret Hamilton were kind of railing against. So, interesting stuff during, you know, the birth of our discipline, I think.

Saleem Siddiqui: I have a theory, and I want to hear your feedback on why...or your thoughts on why it might have, in a sense, might have become less mainstream or less popular, this whole discipline of test-driven development over the decades. It's all about fast feedback, right? So when we were doing punchcard development, feedback was measured automatically in hours, if not days. If you were lined up behind somebody else's stack of punchcards, you could be told, come back tomorrow for your results, so you wanted fast feedback, so there was this natural tendency of how can I make my programs run faster? Of course, that's what it's all about.

I think when computers obviously got orders of magnitude faster, some of that emphasis that nobody waits, hopefully, days for their feedback, so now the question is between what we would prefer in the test-driven development realm on the order of a few seconds, at the speed of the developer's inner loop, versus anything longer, right? Minutes, we try to shy away from minutes, but if you were coming from...or comparing yourself for, well, those people had to target feedback not getting to them until the next day, so we are not waiting 'til the next day, so maybe this discipline of test-driven development isn't all that relevant today. Versus we would argue, or certainly implore that you could always work at a pace where feedback is as fast and as ready as when you want it. If it's really in the order of seconds, if that's how fast you're developing in that inner loop, then you should demand it. And your process is just as important, and you should argue for, labor for, lobby for those kinds of architectural mechanisms and practices that give you that fast feed, right?

I think some of that dilution of emphasis probably has come from computers that have gotten awfully faster, and developers are, in some sense, inclined to be less demanding about how fast feedback could be in the modern world. I wonder if you see things in this light?

Dave Farley: To some degree, certainly. I think that part of the change is probably to do with the increasing sophistication of our software and our computer systems. In the early days of computing, predating me, and I've been doing it for a while, but in the early days, everybody realized that this was incredibly difficult. The people that were engaged in writing software, were, PhD geniuses, dedicated. This was an insanely difficult thing for human beings to do. What we've done, by making computers and software more accessible, and improving the quality of technology is we've kind of lowered the bar. You don't need to be a genius to write software anymore. And I'm not belittling people. That's a good thing. But one of the things that I think with software is we're usually a step or two away from some incredibly deep, complex, difficult problems, you know? As soon as you take a branch, you are immediately into a world of concurrency and complexity of having information in two different places, and how you manage that, and all that kind of thing. So, as soon as you launch a new thread, you're into the same game. Our equivalent of quantum physics is, you know, is that.

We don't think, because of the ease with which we can begin, the ease with which we can get started with the tools and technologies at our fingertips, I don't think we often think about that, and so I don't think we're quite afraid enough of the software systems that we build. So in the olden days, people were more afraid of the systems that they were building because they knew how little they knew how they were going to work.

Saleem Siddiqui: This feeling of always teetering on the edge of the precipice of some kind of failure ought to drive us towards discipline like TDD, and of course CD, continuous delivery and deployment, but it doesn't. At least not as often as you and I would wish.

Why is TDD difficult to adopt but easy to abandon?

Dave Farley: We're all prone to that. At the moment at which you're typing, it feels faster not to write a test. The trouble is, is that you're going to write buggier code, you're going to spend more time debugging it later. We know that there's a dramatic improvement in productivity when you write the tests, but you get that gain back later on, not at the moment when you're typing. Actually, you're getting me to answer the question I was going to ask you, which is, in your preface, you say TDD is often adopted grudgingly and abandoned readily. So, why is it adopted grudgingly, and abandoned readily?

Saleem Siddiqui: It's a bit of that, right? I mean, thanks for doing that fill-in. It's just that feeling of discipline, right? I mean, so many analogies are drawn with whether you're trying to become a healthier individual, right? Becoming a healthier individual is seldom an enjoyable process, whether you're giving up your favorite foods, or going to the gym, or whatever it is, you're trying to do that, at that moment it isn't very enjoyable, let's face it, right? The act of living, becoming better in whatever aspect of your life that you look at, we were, before the recording started, we were talking about music, and how hard it is to create music. The act of creating music, I'm tone-deaf, but I would imagine, having watched musicians, in itself, there might be some measure of immediate gratification, but it's also a frustrating aspect, right? The act of creating something of value is in itself labor.

If people were asking me, hey, how was writing a book, and I'm like, it's a labor of love, and I'm still not sure which part is greater, the labor or the love. I think that applies to anything worthwhile, the act of creating is effortful. You're spot on that effort at that moment can lead to frustration for some people, to the extent that they say just chuck it. I don't want to do this, right? I don't want to write this failing test right now. I can just visualize the code I'm going to write, and I've got this, whatever, real or imaginary manager breathing down my neck, wanting to have this feature finished. I absolutely agree that I have empathy for that feeling of frustration and the external pressure that many developers and people in the software business field.

I actually lean back to other things. I just mentioned music, and I like lean back on more traditional, I guess less cutting-edge things. This is an example I use when people say, oh, I don't have time, or my manager won't like it if I spend time writing tests first, I use the example of restaurants. So, I can go into any restaurant, and let's say I can order a dish, let's say I order linguini. As a customer, as somebody who's paying for it, there's no ambiguity there, who's paying for this, it's me, I can order it...if I'm having linguini, I can order it al dente, or I can order it soft, if I'm allergic to butter, I can tell them, hey, hold the butter, whatever, I have some control over it. What I don't get to do in any restaurant in the world, any food truck, any place that delivers food is say, look, I'm going to go backstage into your kitchen, and I'm going to look over your chef, and I'm going to see how many times, or how much time he or she takes to boil the linguini, and I'm going to basically tell them to do it faster, cut some corners. By the way, I don't get to tell the waiter, I'm not going to pay for this nice china and cutlery you're going to put it on, just slap it on some old newspaper, and cut me $10 off the menu price. I don't get to do that, right? There are some parts of the process of cooking food that are inextricably tied to the safe, healthy, and tasteful production of food. No chef nor restaurant, no matter how high end or how pedestrian it is would tolerate such a paying customer.

Yet in the software world, we are very ready, if somebody tells us, hey, just don't do unit tests, they're not necessary. Some of it is upon our own...how much we value our craft. We have to be like any chef or any food manufacturer who says, look, I can't make food like that, so we should say I can't make software like this. It comes pre-baked this way. The only question there is do you want to do it, or do you not, right? So, I try to use analogies like that when I confront that question, as I'm sure you do as well, that hey, this is frustrating, and I'm going to be asked to not do it anyways, we should, frankly, take a little bit more pride in our own craft.

Dave Farley: We're in danger of too violently agreeing with each other through the course of this conversation. For people that don't know me, I run a YouTube channel, and I did a video on almost exactly this topic a little while ago, and one of the things that I said is when somebody asks me to estimate some code, I don't say, well, last week I typed 3,000 letter Es, how many Es would you like me to type next week? But software developers all of the time say, well, I can do it for this if I don't do any tests, or I can do it for this if I cut corners and don't do any good design, or I can do it for this if I don't do any refactoring. So, why do we do that? You know, we are complicit. I get pushback all of the time from the teams that I work with, oh, we'd really like to do these things, Dave, but our product owner or our business or whoever is telling us that we've got to deliver all these features. The answer to that is that the data in the "Accelerate" book by Nicole Forsgren, Gene Kim and Jen Humble says definitively that teams that work the way that we're talking about, you know, spend 44% more time on new features than teams that don't. So, if your boss is saying build more features, the answer is yes, that means I need to do more testing, I need to do more good design, I need to focus on the quality of my output more, and not cut corners. 

I agree 100% with what you said, we have to take on the professional duty of care, the responsibility that that's our job, that's what we do. You don't hire a chef and say, you know, we're going to hire you, but you're not allowed to wash the dishes as you go, you know? We don't do that.

Saleem Siddiqui: Then the next question, obviously, might be, so, why don't we collectively, right, the collective we, the developer community kind of toughen up our spines or say that? And this is sensitive, and apologies to those developer friends of mine who take umbrage at this, I include myself in that. The other aspect that makes test-driven development, in my opinion, harder to adopt and easy to abandon, as I say, is it requires you to be very vulnerable. It is that process of I don't know how this thing is going to turn out. I probably have a good first step in my mind, I can visualize the first red-green refactor cycle, but I'm not sure about the second one, and I frankly don't have a clue about what comes after. If you're a senior developer, let's say you're pair programming, and you and I know some of these practices also reinforce each other, right, you are encouraged to do pair programming, or to utilize test-driven development alongside pair programming, I can imagine how hard it is for a seasoned programmer to admit to a junior colleague, I don't know how this is going to turn out. So, there's that question of being highly vulnerable, right?

I think there's a lot of healthy growth in being vulnerable, and emphasizing the iterative nature of all of software development, and how test-driven development scaffolds you, ratchets you up that ladder. There's nothing wrong with that. But it does take you to kind of take a big gulp of your own kind of ego at first and say, I don't have the whole algorithm fully formed in my head, which is just going to come out of my fingertips. There is nothing like that. At some level, I think...and I hope I'm not kind of making a strawman argument, at some level, I think there's the expectation, especially of senior software developers, that they don't make, can't or won't make mistakes. I think that we should just kind of publicly and vociferously say the whole business, and I thought other people have done that, the thing about software development is we never create literally the same thing twice, right? That's where it differs from widgets, I think. Nothing wrong with widget making. The industry and engineering have had hundreds of years to kind of hone their skills, but we don't ever make the same thing twice. It's not like making an automobile or a hammer. We create new systems all the time. We're solving unsolved problems

Dave Farley: Another point on which I violently agree is, I think one of the profound mistakes that we've made in our industry is to think of our problem as a production problem. And it's not, for exactly the reason you say. Software is unique, really, in the fact that the cost of production is free, or as close to free as making...it doesn't matter. We just copy the bytes, a million times if necessary, it doesn't matter. But it's the act of creation. And I don't think we think about it, we don't think about it that way. I talk a lot about software engineering these days because that's the bee in my bonnet, but I think... I mean, a lot of people, when you use the term engineering, they imagine some big bureaucratic process. But actually, it's fundamentally a creative act, at least, certainly, for software. I would argue for other types of engineering, too, but certainly, for software, our discipline is a hugely creative discipline, and primarily a creative discipline. That gets back to what you were saying earlier about, thinking about the way that musicians create things and bounce ideas off each other, it's one of the reasons that I value pair programming quite so much is that little bounce of ideas back and forth, the conversation that you have, and you get better ideas. The sum of you and I working together is probably going to produce something better than either one of us would produce alone because we get to bounce those ideas around.

Saleem Siddiqui: There's a deeper kind of the point, and I hope we're not kind of exploiting music for our software interests, but somebody reminded me that the best years that the Beatles had were when they were together. Then, of course, many of them had careers after that, more so Paul McCartney than the others, but certainly it could be argued based on numbers and sales and popularity, not as successful as when they were together, right? So, I don't know, maybe...and I'll admit it's a stretch of an analogy, but if we try to tie ourselves to the fact that all software is a creative exercise, and that prima facie is true, right, it's not about copying the bytes over. I mean, cloning a repository is more or less zero cost. That's not what we're talking about, it's creating the code in that repository in the first place. If you accept that that's a creative act, then the act of creating is always by fits and starts. And I think that would apply, I would even argue, and you are as well, to all aspects of creation is just so that in software, the fact that we are primarily, in fact exclusively worried about the act of creating software, right? Copying of software is really a solved problem if there ever was any. So, therefore, we should invest in those tools, those techniques, those practices, and habits that refine this creative aspect, right, and not worry about the production semantics.

I think the word engineer, because of the connotations with another traditional engineering, does create a false mental picture. It certainly creates a production model in our head, which that's not even software's problem, right? Copying bytes has never been the problem.

Dave Farley: We could certainly debate engineering, but then we'd be talking about my book, not yours. But I think this idea of, the exploratory nature with which we grow our understanding of a problem, and evolve our solutions to meet our understanding of the problem over time is a cornerstone of why I would argue test-driven development is so important. Because it gives us that ability, that kind of backplane to bounce our ideas off, and make change more safely, and, and proceed.

Saleem Siddiqui: I would agree. And if you're always teetering between the...or on some precipice, it's good to have some scaffolding in place. Even if you're exploring this unexplored territory, whatever problem space you're solving with software, it's good to have the design kind of unfold in front of us incrementally as opposed to taking broad leaps of imagination, untested broad leaps of imagination, and say this will work. And of course the feedback. I think that's the most mechanical part of it, right?  I have yet to hear a good argument against it. Like if you had a shorter feedback loop, why wouldn't you go for it? Like, isn't fast feedback an awesome thing? 

Dave Farley: I came up with a lovely analogy...sorry, I'm talking about my book again. But if you're tasked with balancing a broom, you could work out, you could calculate the center of mass of the broom, could figure out the dome on the handle of the broom, and figure out where the center of mass passes through that points of contact precisely. You could, with great skill, maneuver the broom so there was no impulse, and it was perfectly balanced. There's one solution to that problem. But if you were really tasked with balancing a broom, you'd put the broom on your hand, and then you'd wobble your hand around. And that's feedback. That's the power of feedback. That's how rockets balance on the thrust of their motors. This is a profoundly more stable, more effective strategy, there are an infinite number of solutions to that problem. The slower the feedback, the greater the move that I've got to know to cope with it. But you know, if I get really fast, short feedback cycles like we're talking about, I can make tiny little moves, and keep everything apparently, perfectly stable.

Saleem Siddiqui: I think it's that realization that we're always exploring uncharted territory whenever we're creating any code beyond Hello World, right? Or I would argue even if you're learning Hello World in a new language, that's] because there are infinitely many ways to get it wrong. I wouldn't say necessarily one way to get it right, but relatively a much more finite and small set of ways to get it right. You're better off having your feedback that as soon as you go wrong, you have a failing test, or some manner of completely unmistakable, in your face thing, with enough detail telling you how and where you went wrong, that you should actually lobby and kind of make sure that is the case. Make sure all of your learning, and as we just said, all of the creation of software is really learning, is supported by that, right? So, I would encourage my software developers to always lobby for it. Whenever they're told, or they feel that they are being dictated to, that's not important, they should just say no, this is the only way we're going to explore this problem space that you have put in front of us.

Red-green refactoring- why it matters

Dave Farley: Yes and we're the experts in software development, that's our part of the problem. So, getting back to your book, rather than philosophizing in general about test-driven development. One of the things that I liked about it was that you kind of really reinforced the idea of red-green-refactor throughout the book. Could you just talk a little bit about the red-green-refactor, its importance, and why you saw that as kind of the cornerstone of your book, I suppose?

Saleem Siddiqui: Just real quick, I actually love that we're philosophizing about it because the book absolutely does not, I hope, doesn't philosophize at all, or not much. There is one section in the preface that says why I believe in it, and why people smarter than I believe in it, but then it says in that section, and so the rest of the book is really about the RGR, as you mentioned. But the reason I wanted to focus on RGR is, I mean, to use, I guess, market speech, operationalizing the act of test-driven development, right? So it's fine, we can talk about it, and again, I do love talking about the whys and wherefores of the test-driven development, but I suspect most of my colleagues who want to learn are interested in the how, like, how do I do it? And the how is very much rooted in RGR. And so, frankly, every chapter, starting from chapter one, is focused on RGR. I make those cycles at the beginning very explicit. Like, each section, in the beginning, there's a section for getting a red test, getting it to green, and then getting it to refactor. Of course, the book picks up pace in later chapters to kind of do that in sequence always, so the sequence is never broken, but they do speed up.

The reason I wanted to dwell on it is just to, as I said, operationalize it. After you kind of get over the inner restraints or skepticism that developers have towards or against test-driven development, then when they overcome that, then they're like, okay, well, how do I do it? Teach me how to do it, right? I mean, and I say that at the beginning, like every skill in the world, you have to learn it. Nobody's born with it, this is not an innate skill. Everybody learns it, so we can learn, you can learn, all of us can learn. So, I think the RGR cycle, and then slowing things down when you need to, and knowing that you can speed up when you need to is fundamental to learning test-driven development as a practice. As opposed to kind of this overarching thing that we believe, at least I believe, and you and I are of one mind on this, that it's really one of the better ways to solve a problem, set that aside for a minute, how do I learn it? I think getting better at the RGR cycle, that triad is, I think, the best learning mechanism that I know of to learn test-driven development as a practice. So, I focus the bulk of the book on that.

Even when, in chapter six, we write a very small JavaScript test harness, I kind of borrowed that, paid homage Kent Beck's book where he builds, essentially, another JUnit at the end of his book, I didn't build it for all three languages, but I did build it for JavaScript, and there are some reasons given in the book. Primarily because it was worth doing, and JavaScript, of course, with the node ecosystem has so many testing frameworks that I didn't want to pick one, and then, again, open myself to the argument that, oh, it only works in this test framework, and not in my framework of choice. So, we build our own test framework in chapter six. There we see a slightly different incarnation of the RGR cycle. So, I use that not just as a metaphor for this fast feedback thing, but as, frankly, as a literal kind of three-step scaffold, if you will, to kind of always be building new things in a couple of different settings. One is, of course, building the test example, but in chapter six, there's also a small test harness that is built using the RGR cycle. So I found it useful, and hopefully, my readers do as well.

Dave Farley: I liked at one point in the book where you use the accidental miss...wrong implementation of your tests so that the tests weren't running to demonstrate the importance of running them and seeing them fail. I thought that was a nice take.

Saleem Siddiqui: Yes, that was important to me, Dave, because again, as I said at the beginning, this book didn't come out fully formed from my head, either, lest somebody out there is under that mistaken assumption. So obviously, the creation of the book, of course, a book that is so heavy with code is itself an iterative process. So, I had reached that point, and there was this opportunity to improve the code, and of course, because we had those tests, we could do that. 

I would say, I would admit, frankly, and without any shame that when I reached that point in the manuscript of the book, the opportunity to kind of teach that lesson presented itself, and I took it. But I didn't know it when I was writing chapter zero or one. For any kind of aspiring authors out there, hey, test-driven development is also good for writing books.

Dave Farley: When Jez Humble and I wrote "Continuous Delivery," we ran continuous integration on the book as well.

Saleem Siddiqui: We do literally eat our own dogwood, don't we? I mean, this is not...

Dave Farley: We do.

Saleem Siddiqui: ...this is not just a way of, without the kind of making it sound too encompassing, this is a little bit of way of life as far as the creative process goes. And of course, I have to thank my publishers, O'Reilly, of course, they have, obviously, all the tooling in place so when you're writing the book that you're writing, incremental manuscripts of the book, you can generate the PDFs and the pubs and all of that, you can see how that's looking, so all of that is incremental, right? I mean, this is not like we live in this realm when we are kind of in public, and then we go do our kind of snarky little things on the side. No, this is literally, when we talk about fast feedback, it does encompass all aspects of our creativity...those parts of our life that are concerned with creating stuff. 

I would say, very kind of overtly, that creating this book was in itself an incremental process, and I was really happy. There's no unit test that I wrote for the book, but it is very much an act of fast feedback, which sometimes means soliciting feedback from human peers, from my reviewers, but very much so also in the realm of software. There are analogs for how test-driven development relates to creating software versus other creative aspects, such as prose.

Refactoring in Test-Driven-Development

Dave Farley: I also liked the way that you kind of demonstrate practically and in a fine-grained way the importance of refactoring. My experience in teaching test-driven development is people often miss the refactoring step, and they tried to immediately jump to the perfect solution to try and get the test passing, and spend too much time doing that. I liked the fact you said the early chapters go quite slowly, and you're hard coding return values, and then show the refactoring steps that get you to it. So, I think one of the observations that I make of people that become comfortable with test-driven development is the way...how small the steps that they take, you know, shrink to you. I'm sure that you and I, if we were working together, would both be making tiny little steps, one at a time, and getting that little validation to kick all of the time as we were working. It's certainly the way that I work, and I'm certain it's, from your book and talking to you, that it's the way that you work, too.

Saleem Siddiqui: Yes. And it's important, right, because as we have said, there are so many ways to go wrong, right? So, just kind of pretending when you're in the G state green that you also can do refactoring. I mean, sometimes, and I say if you...more power to you if you can visualize doing that, and then of course, if you're pair programming, you can run it by your pair, or you can, maybe when you write your next test, whether you're refactoring was done hastily or not will become more apparent. But it's always, I think, helpful, and has been helpful to me to focus on the green when I'm in the green part of the RGR trial, knowing full well that I have the refactoring part coming up right after that, so I'll have an opportunity to kind of mend back all the corners that I cut during my green state. So, it's just knowing that there is a place for everything helps. Go ahead.

Dave Farley: I talk about the three mindsets of the red-green refactor cycle. We're kind of doing, essentially, outside in API design in red, we're doing, minimum changes to get back to safety in green, and then we're doing beautifying and generalizing our design, and making it perfect in refactor.I want that cycle to be really fast and tight, and you know, move really quickly.

Saleem Siddiqui: I feel a lot of liberation once I get to green, to refactor, because I have those green tests, I have almost a license to prettify my code, or if there's, of course, sub-optimalities, be they hardcoded values or an extra loop or nested loop, that I can now unroll into one, I have those tests. If I make a mistake, I can always revert, and go back to the previous state of green, right? So, not speeding up through those two phases too much is actually an act of liberation, right? Once I get to the green, I then have the license to do as much necessary refactoring as I want. So, it's coming, like, it just requires a little bit of discipline to get to green first.

Continuous Integration

Dave Farley: I also liked that in the book, throughout the book, you also keep reminding us to, to involve continuous integration, committing your things to version control as well, so that you've got that step back, you know? If you do screw up, you can just revert your changes, and it's a small step.

Saleem Siddiqui: Thanks for spotting that, obviously. So chapter 13, the penultimate chapter is all about continuous integration, and hopefully, we can talk about that for a few minutes as well. But one of the things I wanted to mention is that when Kent Beck wrote his book continuous integration wasn't as big a thing 20 years ago, literally now, when his book came out. Certainly, continuous development wasn't a thing until you and Jez talked about it, or continuous deployment and delivery. So certainly, there was an opportunity for me to utilize that. Again, it's not about TDD, I understand chapter 13 is its own thing, but those two things go so much in harmony, and it would have been, I think, a colossal waste. I knew this when I was starting the book, that I wanted to talk about continuous integration in the book. In a book about testing and test-driven development, it would be a little bit of a shame, in the latter part of this decade, that I didn't talk about CI.

I knew I was going to do that. But the act of actually committing everything to get in each chapter came a little bit later, I think. When I got to about writing chapter seven, I'm like, this is going to become too much in the CI chapter, if we commit all of the code at once, and then add CI on top of it. How about we incrementally commit everything to Git, and then introduce GitHub and GitHub actions in chapter 11. So, that was a bit of an epiphany for me as I got later into the book. But I'm really glad, the way it turned out, and feedback tells me so. And certainly, I'm very happy, and hopefully, people find that useful, that it is not just that in the act of creating or crafting your software that the test-driven approach helps because software is always changing, right? It's always malleable on those tests, and of course, you want to run them continuously, or go CI. I hope that people find that added benefit that comes in chapter 13 to their liking, and it reifies the whole concept of test-driven development for them as well.

When should you use TDD?

Dave Farley: I was already sold, but yes, I thought that was a nice take that you had that in place. Sorry, there was something on the screen that just distracted me. Sorry.

So I think one of the pushbacks that I see from time to time is people saying that they would do test-driven development if only they knew the solution better. I think this kind of completely misses the point in terms of what we're doing. I certainly believe that test-driven development is much more a design tool than it is a testing tool, and I know that you do too, from reading your book. So how do you think that test-driven development drives the design process? And how would you respond to those people that say, "Oh, I can't do test-driven development because I don't know the answer yet"?

Saleem Siddiqui: My first response to that... Again, this is rooted in vulnerability. I would say, "I don't know it either." And I would go further and say, "I doubt anybody does." I mean, if it's an act of creation if you think there's some sage, some mystique who knows this fully formed solution out there. If there is, let's go up that mountain and ask that person, right? I mean, they can just give me...give us a solution. Finally, we can rid ourselves of all of this tedium and then go on and do something else, right? But, of course, that's a little bit of a tongue-in-cheek answer, but it's rooted in all seriousness. If somebody says, "I don't know the solution," I would say, "I don't either." 

I'd make a stronger statement, "Nobody does." So while we are in the act of creating a solution that does not exist, we are better off using processes that give us rapid, fast feedback, and that we craft in a way that the solution is more emergent than this false notion of some fully formed solution out there that we are kind of getting access to, right? There's this metaphoric disconnect there. There is no fully formed solution out there. We're crafting it as we go along, all of us, collectively. So, we have better server processes that help as opposed to processes that hinder.

The second thing I would say to your point, about encouraging those people or kind of countering this argument, would be to say that "Look, it's..." Even if you could accept for a second that there is a solution out there, a point solution out there, as unlikely as that is, complex problems don't have a single-point solution out there, as you said earlier in your example as well, wouldn't it be better to approach that solution with assured small steps, knowing that we're working towards it as opposed to away from it, or diverting away from it, right? Even if there's a point solution out there, we want to make sure that we're making progress towards it and not going on a tangent. So I would use those kinds of counterpoints.

I would, again, start by saying that I don't believe there is a point solution. I certainly don't believe anybody has that solution fully formed in their head. We're all, in a very fair sense, stumbling towards that. What we wanna do is take small steps...enough steps that our stumbles are less catastrophic, right? If you're always, always stumbling towards a solution, not the solution, then it's better to take small steps, so when you stumble, you're not falling headfirst into something, and that you're able to course correct. I know I mixed a lot of metaphors there, but that's the approach or response I would say is, "Look, we are always in the act of creating light out of the darkness. And it's better done in the small incremental steps of fast feedback rather than whatever else might be the alternative.

Dave Farley: There's another aspect that I think, too, which is, I think it's the testing word. And I think part of the problem of test-driven development is called test-driven development, and people start thinking about it from the point of view of testing. Often, people will think about testing after the fact, you know, as a result of that because that's a natural thing to think about when you're talking in terms of testing. And so, what that means is that they're looking to, "Oh, well, I've got to have the solution in mind in order to be able to imagine what it is that I need to test." And when you do that, you get much worse tests. Even if you'd write them first, you get much worse tests.

I think that one of the ideas that it seems to me really drives the design, which I think that you've got nice, just implicitly in the examples in your book. But one of the ideas that really drives the design is you want your test to specify the outcomes from your code, not the implementation. So you want to focus them on what's the desirable outcome from this piece of code, even at a fine-grained level, and not care about how it works. That's a much more durable form of specification, even at this fine-grained level, of what we want our code to do. That starts to drive certain design techniques and things that are really important. So things that you talk about in the book like dependency injection.

Saleem Siddiqui: Correct. Sorry, I was gonna say, that's the point that you, in one of your recent videos, brought out very well, which I think you were testing something like a web server, which shouldn't return localhost when it's run in a localhost fashion.

Dave Farley: Yes.

Saleem Siddiqui: Even that test, the outside in test that you showed, clearly it was written after the fact because there was so much scaffolding, so much setup that needed to happen that it really couldn't have been written in TDD. This is an example. Partly I think, and I will admit, somewhat facetiously, that I wish it wasn't called test-driven development, and somewhat test-driven design would be a better thing. Whatever it still would be the same acronym. But the act that it's focused on design and testing is really a means to an end, which is something that I cover both in the beginning and at the end of the book in Chapter 14, coding can be direct. In a very literal sense, we are tasked with and frankly, paid for writing better quality production code. Testing is a means towards an end. Nobody mistakes that it's an end unto itself, it's a means to an end, but it's a pretty darn good means to an end.

Then the whole notion of design is central, front, and central in test-driven development because, as you said, it is very outcome-based, even at that small level of granularity when people think, "Oh, TDD is about writing unit tests first." Unit tests are by definition small. So how much outcome-based tests can you write?" It becomes an implementation matter. And then you say, "No, it doesn't have to be. In fact, it shouldn't be well-written TDD." Unit tests are very outcome-focused. That frees you into finding an incremental, simpler, the simplest thing that works, kind of, designed to fulfill that outcome. When you kind of do that mind shift, adopt that "aha" moment, then you do decouple, even at that level of granularity, the unit tests from the implementation of the code. Of course, that comes very starkly in your face, in the refactoring phase, where you have full license to change the implementation of, frankly, either side, the unit tests or the production code, as long as your tests agree. As long as you don't change the behavior.

So that does separate, as you say, the outcome-based style, the unit tests, and test-driven tests. Really, it should be written, versus the implementations. You have to write some implementation at the end of the day, or at the end of the unit test to pass it. But it's a couple. The tests shouldn't suppose that the code has a loop or calls recursion, or whatever have you. That's up to you to decide in the latter part of the RGs. But if you're writing the test, the test should test the outcomes.

Dave Farley:  Absolutely. I think that's one of the superpowers that advanced practitioners of this start to perceive, is that when you really buy into that kind of idea and start doing that, it has a profound impact on the design of the code that we produce. I'm a longtime practitioner of test-driven development, but my code looks different since I've been doing that. But after, I had a funny experience when I was writing my book, where I tried to write some bad code to demonstrate, poor cohesion, bad separation, and concerns, all of that kind of thing. I started writing it using TDD, and I couldn't. It wasn't possible for me to write code that was bad enough to demonstrate the points that I was trying to. Test-driven development just prevented me from writing code that was that bad. And there's not much else that's like that.

Saleem Siddiqui: There are good reasons for that, David. And when people say, "Well, how does it work? Where's the secret sauce?" And I say, "Well, there's really no secret sauce to it." Or if there is... And I think you talked about it in one of your YouTube videos as well if you were gonna display it in your head, right? If you're gonna end up with spaghetti production code, and you were gonna arrive at it with TDD, what would you have written first? A test. And how do you think that test would look? Worst spaghetti, right?

Dave Farley: Yes.

Saleem Siddiqui: And who in their mind would write a really bad spaghetti test, unit test code for TDD? Nobody would do that. Nobody would subject themselves to that kind of torture.

Dave Farley: Yes.

Saleem Siddiqui: So, you just wouldn't. Therefore, you don't end up with spaghetti code on the production side because way before you ever were done with that failing test, you would have stopped and said, "Wait a minute, this test shouldn't have 17 lines of setup, or five or seven" You wouldn't do that, right? So therein lies the secret sauce, that you would write a test that was readable, manageable by you, comprehensible to you and your peer programmer, and therefore, when you're about to make that test go green, you wouldn't have the need to write spaghetti code. Really, that forcing function, that is it, it's essentially a forcing function. By forcing you to write tests first, it prevents you from having a spaghetti code on the production side because you would have had way before then, the spaghetti code on the test side, which you just wouldn't tolerate as a self-respecting developer.

Dave Farley: Yes, absolutely. It plays to my laziness. I don't wanna type that much stuff when I'm writing a test. Absolutely.

Saleem Siddiqui: It really turns that laziness into something productive, right? No developer would write hundreds of lines of unit tests before turning their attention to production code. They just wouldn't do that. If for no other reason, the feedback would have already have become longer, right? So there are those built-in mechanisms that shy you away from the bad code. So when you try to use TDD to write bad code intentionally, as you did for your book, you find it's pretty darn hard, right? The discipline made into their practice just prevents you from doing that.

Main takeaways from the book

Dave Farley: Well, I think we're probably coming to the end of our time, and I've had a great time talking to you. I got one more question that we can kind of refund for a little while. So what do you think are the most important ideas that you hope your readers are gonna pick up?

Saleem Siddiqui: So number one, I hope they give test-driven development a decent shot. I hope they can rise above some of their own reluctance and adopt some of that vulnerability within themselves and amongst their teams to adopt it. I would love if people could create book clubs after a fashion, after that. That would be kind of the holy grail for me. I would be really honored and elated if somebody said, "Hey, my whole team is exercising your book or following your book," as I call it. "Which means reading it and writing the code this week, and then we'll give you feedback." That would be my first wish because I think trying it out is the best way to learn it. Like any skill, you have to learn it by doing it. Nobody can preach it to you. You can't imbibe it. You can't put headphones on in bed and wake up in the morning a TDD expert. It just doesn't work like that. So that's my first hope that people give it a shot.

The second thing that I do hope, that engineering managers and managers of all ilk, for developers, which I think they should also encourage their teams to practice TDD, and hopefully, through this book, is seeing this as an integral and, frankly, inseparable part of the act of creating software. So this is a non-negotiable thing, right? And of course, I talk about that a little bit in the book, but elsewhere as well in other talks that I've given, that with the ubiquitous nature of the cloud, not just of course, as a deployment platform, but now with no sequel, no code, and low code, coding environments, is another danger, right? That person will, even those who might have practiced TDD in more traditional settings, would kind of veer away from that. When you're writing lambdas or serverless code, that TDD would even get further diluted. So I hope that engineering managers and senior developers, and people at that level of seniority, keep encouraging their teammates, especially the junior teammates, to keep learning test-driven development in these new programming realms that we're facing.

So how do you unit test and test drive your lambda? Like, that's a question we should ask. I had this right before we started. This is directly from AWS documentation, the step function, it says, "You should ensure your state machine logic is idempotent, and should not be affected adversely by multiple concurrent executions of the same input." My question is, how many developers are test driving their state machines, who are writing step-functions out there? This statement from Amazon directly, it's almost a direct... It's imploring developers to test their code, but I wonder how many of them are out there doing it? I hope a lot of them, but I suspect I might be wrong. Like, this whole adoption of a serverless time-based architecture is, wrongly in my opinion, diverting people away from test-driven development. So I hope the people in places of influence and power read my book and then reinforce the principles in these new development paradigms that are upon us.

Dave Farley: Yes, that's a very good point, is that I think that one of the ways in which we as an industry are not great at learning really is we keep forgetting some of the principles that matter. Whatever software you're writing, for whatever purpose, you need to verify that it does what you think it does, because all of us, if we've written any software, ever, have had the experience of writing something and thinking, "Oh, I know how that works." Then it doesn't, it does something else. So we wanna verify that. Now, all we're talking about is how you do that efficiently, and test-driven development is the most efficient way of testing these sorts of things, whatever the technology. And so, as we've already alluded to, you know, even in technologies where there wasn't an easy way of doing it, I've tried to struggle to get to achieve a way of doing it.

I did some stuff relatively early on with JavaScript when there weren't any frameworks that really supported TDD and JavaScript in a browser at the time, and we ended up abstracting away the browser in order for...We gave a facade on top of the DOM so that we could test the code. If you believe in this sort of stuff, then you're going find a way and it's really important. So, it's similar to continuous delivery. I was talking to people using packaged software and applying some of those ideas. We learn lessons in software development, and they are often, maybe even frequently, more generally applicable than we think they are. They're rarely tied to a single technology...

Saleem Siddiqui: I agree.

Dave Farley: ...or a family of technologies.

Saleem Siddiqui: Yes. As I said, the subtitle of the book, it's about decluttering your code. Software is forever entropy. This is a fact of life in physics, and not just in physics and software. Unless we change the complexity, code does get cluttered, and it's upon us to adopt. It's upon us as practitioners to adopt all the measures we can. I think I say something akin to that in the book. It's not a religious belief for me. If something better than test-driven development comes up, I would be happy to adopt it. It's just that I haven't seen anything as good and the need hasn't gone away. The need for fast feedback, the need for making sure that software, to your point, is fit for purpose, and does the right thing, and not because I suspect it does the right thing, but wholly does the right thing. Those needs haven't gone away. And to my knowledge and to the knowledge of many people, test-driven development is the most effective technique to crafting software. So, therefore, we adopt it. It's a means to an end. If a better means shows up, we'll adopt it. But until then, let's learn it and practice it.

Dave Farley: But we don't have one yet?

Saleem Siddiqui: We don't have one yet.

Dave Farley: Well, that's probably a great place to finish up. I'd like to thank you for chatting with me about your book today. I'm recommending to folks, if you're interested in the stuff that we've been talking about, check out the book and practice some of the exercises.

Saleem Siddiqui: Thanks, David, and thanks, GOTO for facilitating this.

Dave Farley: It's a pleasure. And thank you for writing the book, and it's been fun talking to you about it today.

Saleem Siddiqui: Likewise, the pleasure is mutual. Thank you.

Dave Farley: Thanks. Bye-bye.

Related Posts

Recent Episodes

Our Books

THE ART OF STRATEGY

Erik Schön

Buy the book

Chaos Engineering: System Resiliency in Practice 1st Edition

Casey Rosenthal

Nora Jones

Buy the book

Graph Databases: New Opportunities for Connected Data 2nd Edition

Jim Webber

Get the free eBook

Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith 1st Edition

Sam Newman

Buy the book