How to write better code (by making it more testable)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so in this video I'm going to demonstrate how we can take some relatively untestable code and evolve it into something that's more decoupled and easier to test now why should we care about this well testability is a leading characteristic of clean code well-designed code is easier to test because there are fewer hard-wired dependencies and there are fewer tight couplings now when you first start learning to code your main concern is just to have something that works but as a professional that's not really enough you have real customers that are using your product hopefully so you want to have a high confidence that it works and so you need good test coverage and also have good test coverage you need testable code on top of that you also need code that's easy to maintain and extend and by making it more testable you're going to improve those two things as well so let's have a look at this first example that I've put together it's quite simple and it's kind of somewhat contrived but it's a good starting point for us to see how we can make untestable code testable so this is my starting point my program consists of one function that does the following it fetches a list of jobs from github and each one of those jobs visits the company home page and extracts its title and a summary the function that returns a list of companies with their summaries and then my program just prints it now there's no reason why you'd want to do this in the real world but that's not really the point here we can still try and test that does what it's supposed to do what a test do I currently have I have a basic integration test that just calls out to the function and verifies that we get something that looks like a list this asserts that my code correctly integrates with its dependencies in this case github and these company websites I can't specifically make any assertions on what the results going to look like because I'm calling out to these first parties and I don't have any control over what jobs are going to be returned for example let's give them a run they should take the seconds so what if I want to test that given a set of github jobs and some company web pages our function produces the expected result now the best way of to do this would be to write unit tests unit testing is a type of testing where you're verifying the behavior of a small component of your application in isolation this typically means eliminating any network chords so that you can control the input in our case is these are the calls out to github and these company websites now how can we unit test this code at the moment were a bit of difficulty some of you might be thinking it's fine we can just you know test this by stubbing or mocking out the request or get function however I'm going to argue that you need to stub out something as low-level as a library function that just makes an HTTP request you've got a code design problem here and on top of that the logic in your test is going to get complicated you're going to have to write a mock that has a fake implementation that returns different results based on the improve the provided ideally we want to keep the tests as simple as possible by not putting too much logic into them so one of the reasons why this code is untestable is because it violates the dependency injection principle which states that high level modules should not depend on low level modules now the classic metaphor for this is soul during your kettle directly into the mains as opposed to using a plug in a socket instead our code should depend on abstractions and an English abstraction means something that's not concrete in this programming context here our get company's summaries for jobs function currently depends on concrete implementations for things that make HTTP requests now the dependency inversion principle is one of the five design principles intended to make software designs cleaner traditionally is to find in the context of object-oriented programming but it can also be applied in the functional context too and in my first version of this code if I want to test this without having to make HTTP requests I've got a bit of a hurdle to overcome so that's the first problem the next one that jumps out to me is that we have IO operations intertwined with business logic if we have a look at this function here we see we're making a network call to retrieve our jobs from github and then we have a loop and that loop we grab the web page and then we process it and then we grab the next web page and then we process it so that's what I mean by intertwining IO logic specifically in the case of this function so another way to improve the testability of the code would be to introduce a clear separation here the way to do this would be to gather as much data as we can up front and then pass it through to a pure function that performs the logic and that's what I did next I've split the function into two steps note that we're now interesting over the jobs twice wants to retrieve the webpages and another time to process and build our results but we haven't increased the algorithmic complexity here so I'm not too worried about that the additional loop helps us achieve the separation that we're looking for the next thing I can do is take step two and pull it out into this pure function that I was talking about and this is really good because now we can simply pass it an input and gain output and there's no side effects to worry about which is kind of the beauty of pure functions we can easily test this by providing some test input and checking that we get what we expect so this is a really useful tip if you want to write more testable code try and think about how you can break it down into pure functions crucially our function that contains the logic doesn't care about where the data comes from and it shouldn't let's look at the test now I now have a new test case where I am testing the new function with two variables one of them being a list of jobs from github and another being a list of company webpages now the first web page in the webpage list corresponds to the first job in the job list and the second to the second job and so on as this is a unit test it's not going to take any time to run a tool which is the great thing like unit tests it's now possible for me to test more edge cases if I want see for example I can test following scenarios what happens if one of my company web pages has malformed HTML or what happens if one of the company websites is missing a title these are questions I can now answer by writing tests for my new function all in all were in a much better place in terms of testability but we can still do better currently Lions 24 to 31 are only being tested in our integration test so we can continue to improve this code to allow us to unit test these lines as well so the next thing I did was to pull out the calls to request off get so our HTTP requests into two separate functions one function that returns a list of jobs from github and the other returns of raw HTTP response body for a given URL now this takes us closer to something that complies with the dependency inversion principle by creating two functions we have two implicit interfaces the first is a function that takes no arguments and returns a list of jobs the second takes a single argument a URL in this case and returns a string the last thing I'm going to do is remove our dependency on these two concrete implementations of fetch github jobs and fetch rule response I'm going to achieve this by using dependency injection so again dependency injection is an object-oriented programming pattern but we can apply here even if we're not using objects dependency injection simply allows the dependencies to be supplied to the function as opposed to having them hard-coded like they currently are and we can achieve this by passing a context into our function the context is a simple struct that we can assign properties to in our case implementations of fetch github jobs and fetch raw response in our function we're going to call out to these dependencies by accessing them through the context now it's finally dependent on abstractions it's up to the caller to decide on which implementations it would like to use for these dependencies when running our program is main you want to use the production context in which we caught up to the actual github API when running in a unit test we can use a non production context so let's see how we do that if we look at the test file now we can see that we've got a new unit test except this time it's for the top-level function we create our test context in the body of the test walking out the implementations of fetch github jobs and fetch or response fetch github jobs will return the list specified up here and fettle response will return a string of HTML with the URL embedded in the title now as long as our mock implementations have interfaces that our function under test expects we're all good we've now got full unit test coverage however our unit tests don't cover the real implementations of fetch github jobs and fetch roar response and that's okay because there's no logic in those functions in order to maintain test coverage for these two functions the best thing we can do is to keep running there's integration tests so I'm not going to remove them so finally our code is in a much more testable state it now complies to the dependency inversion principle and we've introduced a clear separation between the IO and the logic next time you're writing code try to think about the things I've mentioned here do you need to inject dependencies is there a way for you to break down your code into pure functions now one way to answer these questions is to try to write the tests before you break the implementation which is something called test-driven development or TDD and that should help you write something that's more testable because you are going to have to think about not hard coding your dependencies for example so hopefully this video was useful to you if you've liked this kind of content press the subscribe button with the bell icon I'll let you know when the next video comes out in the meantime if you've got any suggestions on any topics you'd like me to cover let me know in the comments below
Info
Channel: The Art of Software
Views: 986
Rating: undefined out of 5
Keywords: coding, software, developer, software development, software engineer, python, javascript, software testing, testing software, how to code
Id: 2b2-WPzWl6c
Channel Id: undefined
Length: 11min 0sec (660 seconds)
Published: Sat Apr 18 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.