Test Driven DESIGN - Step by Step

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
the idea of test driven development is that we start each new step with a test we're going to write this test before we have any code to test and unless we're a complete beginner we're going to write this test before we have any idea of the code that we're going to write to make it pass too the clue here is in the name our aim is to drive development from these tests this often poses a problem and often leaves us sat in front of our computer thinking what do I need to test now the blank page problem is a real one in test driven development so how do we decide what to test to start with and what test comes next how do we use our tests to explore the problem and evolve our solution small step by small step foreign hi I'm Dave Foley of continuous delivery welcome to my channel if you haven't been here before please do hit subscribe and if you enjoyed the content today hit like as well in this episode I want to explore one of the biggest barriers to the adoption of test driven development and demonstrate overcoming it with some real code that I'm currently working on the practice of test driven development is actually fairly straightforward but despite this test driven development is difficult to adopt I think that this is for several reasons sure test driven development is a bit more complicated at the edges of our system where it interacts with the world via i o devices of some kind it's also difficult to retrofit to pre-existing code bases but there are some common strategies for dealing with both of these sorts of things and I've talked about those before I don't really think that these are the main stumbling block though I think that test driven development works for some fairly deep reasons but to access some of these depths we need a changing perspective the main problem here is that what test driven development really does always is to surface design problems it does this better than anything else that I know and the trouble with that is that while test driven development may be easy design is not good design is hard but it is worth the effort let me pause here to thank our sponsors we're fortunate to be sponsored by equal experts trisentis transfix sleuth and Ice panel all of these companies offer products and services that are very well aligned with the topics that we discuss on this channel every week so if you're looking for excellence in continuous delivery and software engineering click on the links in the description below to check them out my impression is that there are many programmers out there that don't really seem to pay a lot of attention to design and to be honest aren't very skilled at it as a result so the barrier to test room development is not really the testing it's the exposure to just how bad their design choices are and that makes things more complicated let's be honest good design is hard but tdd helps to point us in the right direction and so can help us to learn to get better at design I talk about this a bit in a bit more detail in this video don't get me wrong I value the tests that I get from practicing tdd but for me the most valuable thing is the feedback that I get on my design I get to be the first person to experience using my own code and I get that feedback before I've even written it because I start by defining how I will use that code in a test I spoke about this process in the first ever video that I made on this channel I've learned a few things about making videos since then but my views on test driven developments haven't changed very much one of those deep ideas at the heart of test room development for me is that we start to see software development as a process of some kind of guided Evolution we're going to grow our software incrementally through many small changes getting clear accurate feedback on its Pro progress after each small step this matters this is one of those to me much deeper principles that test driven development exposes to us it frees us to solve more complex problems because we divide them into many small steps but it also forces us to design in those small steps more incremental steps I think that for many people this is perhaps the biggest and most challenging step from a more traditional ways of working to tdd instead of thinking hard about a problem trying to completely understand it and then getting at a solution that will work and building that solution which is certainly how I used to work 30 or 40 years ago we're going to start before we understand the whole problem and certainly before we have the whole solution in mind this is a big shift and a bit of a learning curve and I think maybe one of the real barriers that people find in learning test driven development the first liberating step when learning test driven development is to stop focusing on the solution and testing the solution instead we focus on the problem and specifying it in some detail in the form of a test everyone makes this mistake at first and imagines the solution in order to be able to figure out how to test it when I teach test room development I recommend that you consciously avoid this very common beginner's mistake instead try and describe the problem with your tests and ignore how you're going to solve it for now try and think of a simple example that would demonstrate that your code is doing something useful start with the simplest possible case of something useful that you can think of and create a test for that imagine we're going to write code to add fractions the simplest case that I can think of for adding two fractions is to add fractions that represent integers so I could imagine my first test starting with something like one plus two equals three I'll start with that test and once I have it and have seen it fail I'll add the code to make it pass I'm using the creation of this test to explore how I'd like to represent this addition that's my real focus at this point not how I will add fractions but how someone will use this code the design of the API to my code if you like this will lead me to build a skeleton of my best guess a design at this point with just enough code to make this test pass once all that's working and looking good I'm ready to move on but not before even at this very early stage I've already been able to make some design choices I've decided in this case that I want a type to represent fractions and that when I add two fractions I will add them as fractions rather than dealing with them as strings or decimals or anything else after a bit of refinement I may refactor my design a bit further and end up with something like this which is really the outline of my first guess at a design for adding fractions by choosing the simplest possible case it's helped me to experiment and play with the basic shape of my code while everything is at its simplest I can change it easily and more quickly and make some design choices and and experiment with them these will shape the next steps by choosing the simplest possible case it's helped me to experiment and play with the basic shape of my design while everything is at its simplest I can change this code easily and quickly and make some design choices that will help me and shape the next steps it's not worth agonizing too much over the design at this point but it's also not worth skipping over it and ignoring this chance this is the easiest time to correct mistakes or even to explore your preferences I tend to start reasonably slowly at this point thinking about the kind of design and where it might go a little bit but also trying not to think too far ahead maybe trying different ways to organize things my decisions are being Guided by the external usability of my code for this simple case but also I'm now just starting to think a little bit about some of the broader structural choices in front of me for this first guess at a solution if you'd like to learn a bit more about test driven developments and maybe try it for yourself I have a free online tutorial that can help to get you started check the links in the description below in the case of fractions I've already decided that I'm going to create a fraction type and that to create one of these I will specify a numerator as an integer and although I haven't done it yet I'm pretty sure that at some point I'll be adding another Constructor that probably takes a denominator soon I've also decided to separate the concerns of addition from that of rendering fractions into a string representation I use the the ideas of managing complexity that I describe in my book as guidelines even at this simple trivial level of code as I said I always try to imagine the simplest possible case for my first test I think it's okay to play with this at this time to explore Your Design but you should avoid designing ahead as I've already said this is a subtle difference at this stage I'm only focused on adding integers I've made some small mental steps ahead strictly this is a risk and I'd advise you to be very cautious of steps like these but I don't really see any reason why I should remove my brain from the process of test driven development so I'm happy to think ahead a step ahead or two but to try and rein in any desire to imagine that the solution to the whole problem so for example if I were only adding two numbers creating a fraction is really Overkill but I'm not I'm working on on something that I know is aimed at adding fractions so I think it would be naive of me to start with a test that confirmed that one plus two equals three using only into integer Edition I am after all using this test as a design tool not as a means of verifying that maths or integers were in my programming language work I'm willing to believe that those things already work so the job of my first test is to help me think about how to structure my code for adding fractions the bear trap here is particularly for beginners is doing too much of that thinking ahead here I'm comfortable with identifying my desire for a fraction type to represent the addition and then through a very simple process of refactoring getting to the decision to separate the concerns of the addition from that of rendering fractions as strings but you could argue that the decision to return the fraction is a string rather than as an integer as another fraction he's designing too far ahead I'm relaxed about that additional step in this case but largely for one reason at each small step I still want to be doing the best job that I can of my design in the context of the problem as it's expressed in my tests so far and no further I'm using the test to design my code and specifically thinking about the interface to my code and making that Pleasant for a user of my code I don't want to design too far ahead but I also count it as a success if my code and tests build up incrementally I don't expect to get everything right first time but I also don't aim to write tests that I know I must revisit um at the point when I write I'm writing them I'm picking tests to advance my design so I want my design to be progressing with each test ideally I know that I want to have a string representation and that that will make this test now at this point a little easier to read so I'm happy with that choice once we're happy with the first test and the design of our interface we can start adding more tests what we want next is a test that will make us explore a bit further into the problem not the whole problem but just a small step and not just a repetition of what we've already done either there's absolutely no value in terms of advancing our design by testing at the addition of two plus three equaling five for example that's just going to pass and it's just testing exactly what we've already just tested so we want the next step that's not too complex but not too simple either sometimes it can help your thinking to sketch out a few next steps for some kinds of testing for adding fractions my progression would probably be something like this next we probably want to add some really simple fractions maybe one third plus a third equals two-thirds this forces us to add that denominator that we envisaged earlier and to add the rendering of fact fractions with a numerator and a denominator but we're still avoiding some of the more complex bits of adding fractions which will as presumably come later next we could add fractions that Force us to calculate a lowest common denominator and so on until we get to more complex cases that cover all of the types of fractions and and their addition that we can think of let's look at a slightly more realistic slightly more concrete example this is some code that I am actually in the middle of writing at the moment we have a Wiki available to patreon members above Mercury level that acts as a kind of guide and index to a lot of our content it's called msec one of the ideas of the msec is that you can follow a trail through the content to follow a strand of a particular topic want to learn how to build a deployment pipeline follow the red track want to learn about teamwork and Leadership Follow the yellow one and so on I'm playing with this idea of trying to generate a kind of Tube map of for the content to replace the rather manual Wiki markup that we're currently using to show the tracks so I'm playing with some code to to try and get this Tube map idea in place the wiki stores content in a relational database and we version controlled it to help automate the deployment as I hope you would expect of me so I'm thinking about reading the content from the Version Control database data and then generating a map of that content later I will try and render page specific images of the map and markup to make the maps clickable and that I can add to each page and so improve the navigability in m-sec a fun little project of course I'm using test room development I always use test stream development and I'm exploring so I didn't want to bake in lots of Technology into my tests I decided that for this job I didn't really need to access the real database I can use the version control dump of the content and pass that as a file it's pretty basic string manipulation and so I don't really need all the complexity of dealing with SQL for this yes I know this is a more coupled way to do things and I probably wouldn't choose to do this for something bigger and more complex or less throwaway but I can live with it for this job basically I've decided that I've gained Nothing by using the real database here other than making the infrastructure a bit more complicated I don't really recommend this kind of backdoor approach in general though but here I think the risk I'm taking is that the format of the SQL in the database dump changes and breaks my code I can live with that for this case so I decided to read the database file directly instead of accessing the data via a database I also knew that I didn't want to be coupled to real files in my tests I wanted to be able to move faster than that so I decided to assume that the content of the file that I need to parse as a strict will be presented as a string and the starting point for what it is that I'm testing so now my first test uses a hard-coded string that's based on the format that will be in that file and I've got other code that will sort out the files and and provide them to this piece of code I've made my life and my test a lot simpler as a result of these choices my job now is simply string parsing and not file or database handling now I can write tests in my heart's contents without accessing the databases or files these decisions to make my code easy to test have shaped it a bit once again I've made a few starting assumptions that have shaped my design as a result I've come up with something that I've called a Wiki data parser which takes Wiki data as an input the contents of the file or the database dump and basically just filters out all of the stuff that I'm not interested in in that in that piece of code it starts off at this early stage in the design by just isolating the text that represents the page table in the database next I have something that I've called a page reader that takes a table of pages as input and creates a list of value objects each representing a single page but that description is with the benefit of hindsight at this stage in my test driven development Journey actually what I have is just something containing a single page we haven't got to multiple Pages yet remember we're starting with the simplest possible case and progressing in Tiny Steps this test took a little time because I was exploring the design and how to organize the relationship between the different parts of the parsing job but I don't think that I'm over engineering here because I'm not predicting where it will go next only that I prefer modular code with a good separation of concerns and I want it to be easily testable the next step after this is pretty obvious what if there are many pages well in computer counting terms two is the same thing as many so what if there are two pages we don't want to test lots and lots of iteration if we can Loop over two we can probably Loop over 2 million or at least over Max into pages it's nice to keep our tests small fast and focused on what really matters if you're worried about how to protect your code against looping in lists that are bigger than maxint specify what you would like your code to do when that happens and write that case as another test ideally do this in in a way that means that you don't have to evaluate it by blooping over Max in times here's my next test it's based on a different set of data but it's almost the same as the first but now we're confirming that We're looping over rows of pages to defining the wiki data but by checking that we get the second page not just the first Tiny Tiny Steps but now I've got two passing tests and after each one my code did a little bit more now I'm ready for what I'm really interested in here starting to think about mapping the links between the pages already I have some test data for two pages so I can probably use that again I've decided that what I would really like is to build a kind of node tree a graph of pages a directed graph remember ultimately my aim is to draw a picture of the navigation from a particular starting point so given my map of all the pages I probably want to be able to Define that starting point so I'd like to request a particular starting point in the tree so the simplest Next Step that I can think of is to check that if I ask for a particular root page from the list of pages that's what I get this test has made me add a new method to the interface to my code I still haven't decided how it works yet but I've specified exactly what I want to happen in this case and that's enough to be able to define a function that Returns the specified route in the in that tree of pages next I'd like my root page to have some children so here's my test for that and for this I need some cleverer test data because the links that define whether pages are related in a parent-child relationship are defined in a separate table so here's the new data with some links specified this time and my tests once again tests the simplest Edition that I can think of so we can have one page that's linked to another this is a small step again but now we need to start adding a bit more complexity to the system under test to make this test pass we'll need to think about this and decide how to implement it notice that none of my tests are testing implementation detail I could throw away all of my code and rewrite it in a completely different way and these tests would still be correct but the tests are very specific about what the code is meant to do this is the mark of good tests in my opinion this latest test is now forcing me to add code to find links between pages and represent that link in my graph of pages by adding linked Pages as children to the current route but it doesn't tell me how I have to achieve that so I'm pretty happy with this I'm not quite so happy though with the tests themselves there are lots of repetition in boilerplate here so I prefer to make them a bit more focused and readable so once all of these tests are passing remember always refactor while the tests are passing I'm going to generalize the test code a bit here's what I ended up with I've created a couple of helper functions in my test and I think the tests now look a little bit tidier now and future tests will be a bit easier to re to write so what's the next small step from here once all of these tests are passing how about multiple children so far we only have a single layer of hierarchy so the next obvious step after that is to build this as a proper graph of links we need to test Children of Children at this point I decided that I'd write this as a recursive function so now I knew that I needed another test to prove that the recursion would end you should always test that recursion ends so I created some circular links that and wrote a test for data that looked like that at this point my graph was building nicely but remember this code is to do a real job and I noticed that the names for the pages that I grabbed from the database dump contained underscores to represent the spaces between words so I parse the names and and added a test for prettier names too these last two tests in particular show the little dance between the code and the test the test is the specification it doesn't and shouldn't know how the code works but as we explore the design through these tests we can and probably should change our minds about the design and so think of new tests to add I like to think of my tdd tests as mini specifications that tell me what the code is meant to do but I also expect my view on my code and my design to chain I like to think of my test driven development test as many specifications that tell me what the code is meant to do but also I expect my view on what my code is meant to do to change as I learn more through my exploration of the problem and my design driven by these tests I hope that you've enjoyed this little exploration of my approach to using tests in tdd I know that this project is very simple but I think that all the principles remain the same for much more complex code at least that's the way that I build both complex and simple code thank you very much for watching and if you enjoy our stuff on the continuous delivery Channel please consider supporting our work by joining our patreon community there's links to this to that in the description below too thank you again foreign [Music]
Info
Channel: Continuous Delivery
Views: 19,389
Rating: undefined out of 5
Keywords: evolutionary design, what is evolutionary design, continuous design, tdd, test driven development, test driven design, what is tdd, what to test next tdd, incremental design, incremental vs iterative, extreme programming, programming, computer science, software engineering, software development, Dave Farley, continuous delivery
Id: -f_HgWbomCI
Channel Id: undefined
Length: 25min 46sec (1546 seconds)
Published: Wed May 31 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.