#19 - Dart Testing Explained - Unit, Integration, E2E and why you should aim for 100% code coverage

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what is going on everyone i'm wikid welcome back to another awesome tutorial today we'll learn the difference between all types of tests as well as discussing why it is super important to test each and every line of code you write is it worth spending the extra time to write tests for our code we'll find this answer to all of those questions soon so without further ado let's dive ourselves into the tutorial now you need to realize that tests are not exactly something that you'll learn and understand the first day you get to see them they might seem simple at first but i can assure you that alone they are as detailed and complex as all of the chapters of dart language we discussed up until now if not even more abstract in this tutorial we'll only scratch the surface of tests observing their massive importance because this is essentially what you'll need to know at first for now let's start our long journey by looking at a package i specially created for this tutorial let me start by introducing you to the general idea behind this package so inside the lib folder we have a library called geometry a library that will contain multiple implementations of different geometric shapes for now it only contains a triangle shape but in the future you might imagine it may compact with a square shape a circle's shape rectangle shape etc as you know from the previous tutorial these implementations represent separate libraries and they are placed inside the src folder they are also exported by the barrel geometry.dart file representing the main library now you might remember from college or high school that there are multiple ways we could describe a triangle of course we could do it by specifying its three coordinates but we can also describe it by specifying the length of its sides or by measuring the length of the base and the perpendicular height to it there are many more ways we can describe it but i've chosen those three for this tutorial all fields are nullable since all fields are optional a triangle that is only described by its lengths can have the rest of the fields set by default to null or they can be all populated as extra fields now due to all of these options we have into creating a triangle i decided to code three named constructors one for every case each triangle also has a triangle type field that will be set accordingly when created note that not every input you'll give will necessarily represent a triangle for example when the lengths are given in order for them to denote a triangle the triangle inequality states that the sum of the lengths of any two sides of a triangle must be greater than or equal to the length of the third side this condition is verified right here obviously lengths need to be positive numbers by default so i also had to incorporate these conditions therefore if an user inputs lengths of value 2 4 and 1 the triangle isn't valid since 2 1 is not greater than 4 so there are conditions for every type of triangle we can create therefore it makes sense to have a fourth triangle type called invalid that will be assigned automatically to the triangle of which some of these validation criterias are not met now we're sure that no matter what the input of the user will be our triangles will be either valid or invalid so we'll proceed from here right now it was the moment that i went ahead and started implementing the functionality of a triangle i mean yeah sure we managed to translate a triangle from paper into dart but what for if we cannot calculate its area for example now again related to how we instantiated our triangle there are multiple possible ways on how we can calculate its area if the lengths are given then it's this formula if the points are given then it's this sum divided by two if the base and height are given well we can calculate it directly by using this formula but what is it going to happen if the triangle is invalid and we try to calculate its area well we could return null or zero but that would not be professional instead we should throw an exception and in order to throw a custom exception we need to create one so i went ahead and created a new folder called exceptions and named it invalid triangle exception that's all now whenever we call the area on a triangle type that's invalid we'll throw this exception so now that we finished implementing this program what's next well we need to see by ourselves that it works as expected right indeed we'll go right ahead into the main file instantiate some triangles calculate their areas then verify it with a calculator or another tool and see if they match right that's reasonable if you're only going to do this once but let's analyze what is going to happen if you'll have to do this multiple times and why would you want to do it multiple times well there can be multiple reasons for that you're working on a team perhaps someone else modifies the code a little bit perhaps to implement a new feature pushes the changes to git and creates a pull request then the next day you'll have to take a look over the entire code again just like you'd implement it the second time or you'd create again some new examples verify if the real areas match the one resulting from a program and then perhaps accept the pull request to integrate the changes into the package or you could be working on your own on another implementation that may interfere with the existing code when you'll test the new implementation you'll also have to retest the first one hoping that nothing went wrong you might see where i'm heading with this this thinking is absolutely flawed and you should get rid of it as soon as possible believe me it's not rocket science imagine you had to write this program for a contest you upload it to git and now the jury will clone all repositories to see how each of you did perform do you think that they will have to open your project go into your main file search for it paste their own set of inputs by hand import all the libraries then run the program in order to compare the outcomes to the real result of course not instead they'll have a set of predefined tests that they will loop to all submits run them all with those tests compare the results and rate the contestants accordingly this will allow them to rate 100 contestants in a matter of seconds now think of how much it would have taken to do that manually as i previously explained this is exactly the difference between manually testing your implementations again and again every time you come up with a new feature instead of writing tests that will glue and back up all your existing implementations all together so that you'll only have to write tests for the new implementations let me put this into perspective for you here's a representation of the number of features you might want to implement in your app there are two paths you can take from now on paths that will seriously dictate your development workflow in the future so let's design them one next to each other you'll implement the first feature and you have to see if it works as expected student a will open the main file create some examples run the program and note the results student b on the other hand will create automated tests think of all the possible cases where the program might fail test them accordingly so that in the end the implemented feature is 100 accurate and fully working now for the first feature you might think that student a might complete the verifications faster and that might be true in some cases but let's think the other way around the code he wrote inside the main file is more or less now useless i mean it kind of provides some assurance to the student that he did good but from the app's perspective it's completely useless it doesn't bring any value to the game and for me what doesn't bring any value should be immediately omitted it's like during an exam you're writing all your thoughts and examples on the paper hoping that you'll make your teacher give you more points for that it doesn't really work like that what the teacher cares about is that the result is equal to what was expected nothing more nothing less your package should only contain stuff aiding towards the functionality of your app and not some random side notes student b on the other hand even though he might have spent more time writing all tests doesn't have any extra unnecessary code in his project plus he knows 100 that all his tests are correct and back up the entire functionality of the feature he just created to me this is priceless it doesn't matter how many extra minutes i spend on writing tests if those tests are automated and can guarantee that absolutely each and every line of code i wrote acts and behaves as expected then i'll definitely go on with the student b approach and take in mind we're only at the first implemented feature what is it going to happen when we'll implement the second one you might ask well if the features are independent then probably the exact thing that happened previously however when developing an app there is a high probability that when you develop the second feature some parts will interact with the first one will depend on the first one and perhaps will influence the first one well student a will have to go back into the main file undergo the same procedures as he did with the first feature write some other tests for the second feature as well as checking again if the first feature still works as it should student b on the other hand doesn't care about the first feature anymore since it's 100 percent backed up by tests so he only focuses on feature 2 and perhaps on all the interactions that may have with the first one at the end student a will have even more wasteful code and student b will be more and more sure on himself that it's on the right track speeding up to the 10 feature i can guarantee you that if you'll interview both students right now student a will have a confidence level of around 20 to 30 percent regarding its app since his way of verifying the functionality of all features will definitely decrease over time the unnecessary code he wrote in the main file let's be honest won't look anything close to structured it is going to become harder and harder to read and understand what's happening not only inside the main file but inside the entire app on the other hand student b will have a confidence level of around 90 percent a value that is not only backed by the code coverage achieved by writing tests but also by the cleanliness the stable structure the level of organization tests offer in his application having this said it's up to you which student would like to be i've been student a for a long time because this is what they're teaching us in school unfortunately however noticing the advantages tests bring to the table i need to admit i definitely changed my mind and perspective and i advise you to do the same yes test might be harder to grab at first but so is everything else in life you'll eventually get used to it and laugh at the moments where you were testing the functionality of an app inside the main file and speaking about how hard it can get to create things in life this video is also an example of that so if you find this tutorial useful and you appreciate the way i teach these concepts please make sure to share the video hit that like and subscribe button and consider hitting the notification bell so that you'll know when i post a new video also make sure to follow me on twitter at let's get wicked this is where i'm at most of the time now having this said let's take a look at our package right now and delete this atrocity from inside our main file matter of fact we can actually close the main file because we're not going to touch it anymore ever again in dark ideally in order to have 100 code coverage that is to have a test for every line of code you write you'll have to well clone your entire lib folder structure containing all implementations right inside the test folder and then prepend the names of all your files with an underscore test every file you develop should have a test file in which will write the test sheet making sure the implementation works just as it should be so in my case you can see i have a triangle underscore test.dar file in which i wrote all the tests i had in mind the file is already getting pretty big but so is the confidence level highlighting that we have a stable and correct implementation of our triangle class now what you need to know is that the tests you're seeing right here are all unit tests unit tests represent the easiest and most straightforward type of tests they focus on verifying the smallest piece of testable software such as a function method or class as you might imagine more than half of your test will be unit test since well there will be a ton of functions classes methods variables that you'll create and all of those need to be properly tested in my case i need to test whether a triangle types were assigned properly depending on the variety of inputs users can provide to the program as you can see the entire test file is just a huge main function ironically in which you can declare multiple variables set them up specifically to all possible use cases and scenarios then enumerate all in separate tests the main idea here is to think of every possible outcome of every variable class or method you implemented for example my triangle class could hold a triangle that is valid or invalid and this is influenced by the inputs users can provide to my app in order to write tests the general idea you should follow is you should think of absolutely every possible scenario that will work that won't work and that may also crash your app therefore i thought of the majority of those different valid and invalid cases created a triangle for each and every case and tested if the expected triangle type matches the right logical one this was achieved by using the expect function as you can see right here the expect function thus take two required positional arguments the actual and the matcher which kinda makes sense right the actual is your implementation that you want to test whereas the matcher is the true result it needs to match note that each test has also a long description a long title that is 100 recommended to be as descriptive as possible so that if a test fails in the future you'll know exactly what needs to be checked out you might have also noticed that the tests are surrounded in different groups these do not play an important role in the standalone functionality of test but they make sure the tests look more organized especially in the testing tab of vs code for example in this section i'm testing if the triangle area is calculated correctly based on the triangle types and then i've splitted all of the upcoming tests in whether the triangles are valid in which i calculated the exact area with a calculator and checked if it matches the actual result returned by my area getter and in those triangles that are of course invalid in which i tested if indeed the getter throws an instance of invalid triangle exception all of these tests can be run from terminal by typing in the dart test command or by going into the testing tab and proceed from there by clicking this button either way we have all tests ending up in green color and that's perfect that's the best thing you can see all tests have passed now apart from these tests there are other types of tests like component or widget test tests that basically verify that a component which actually consists of multiple classes behaves as expected a component test often requires the use of mock objects that can mimic user actions events perform layout actions and instantiate trial components for example in flutter mocking means that we don't actually have to press a button to verify that it does what it should we can mock the action and let flutter think that we actually press that button other types of tests we'll see in the future are integration and end-to-end tests as their name implies these verify the behavior of an entire app or a large chunk of an app an integration test generally runs on a simulated or real device or on a browser in case of web and consists of two pieces the app itself and the test app that puts the app through its spaces an integration test often measures performance so the test app generally runs on a different device or os than the app being tested now before i end this tutorial i want to give you a little insight of the coding process and how i will proceed writing some other implementation for my triangle class as well as how easy and straightforward it will be to test out the functionality so let's go right ahead and create a perimeter functionality whenever we call a perimeter getter the perimeter of a triangle will be calculated and then returned by the getter note that we can only calculate the perimeter in two cases when you have either the length of the sides or the coordinate points in the second case we can actually calculate the distance between the points thus the length by using this formula right here so let's create another helper method called distance between two points and return the value denoted by this formula now whenever we create a triangle from points we can also initialize the a b and c side lengths by using the method we just created the perimeter getter is even simpler to implement compared to the area one all we have to do is to return the a plus b plus c parameter on the first two cases if triangle is invalid we'll throw an invalid triangle exception as well however if only the base and height is given for a triangle we can't really calculate the perimeter out of that so we'll have to create a new exception called unsolvable perimeter exception which will throw in this specific case now after we finished implementing the perimeter functionality let's go inside the test section and as you'll be able to see up next not only we have already previous tests that make us more confident but we can also use some of their structure as it's pretty similar in order to implement the rest of the test and use cases for the perimeter functionality so you see even though you spend some extra time writing tests now you can even share some of their structure to create other tests and believe me the process will become faster and faster day by day line by line and i think it is finally time to end this tutorial on tests inside dart language i hope you finally found the motivation to switch from writing unorganized code to writing concise and truthful tests tests that will boost your confidence and development workflow in the long run don't believe me at least give it a try by yourself once you'll be really surprised by the outcome for the next tutorial we'll switch our view to yet other really advanced topics in dart synchronous and asynchronous workflows we'll start with the synchronous one it is going to be a really really important topic to be understood so make sure you have your mind ready to grasp all the important concepts i'll teach you up next as always if you like this tutorial don't forget to smash that like button subscribe to my channel and share the video with all of your friends and colleagues in pursuit of top tier development until next time as always take care wicked is out bye bye
Info
Channel: Flutterly
Views: 1,178
Rating: undefined out of 5
Keywords: dart, dart tutorial, dart programming language, dart programming language tutorial, dart test, dart unit test, dart integration test, dart end to end test, dart testing, dart coverage, dart writing tests, dart test tutorial, dart testing tutorial, dart testing vscode, 100% code coverage
Id: NYi1saTtP-0
Channel Id: undefined
Length: 21min 41sec (1301 seconds)
Published: Mon Sep 06 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.