- When you are writing professional code,
it's important that you cover all the branches
that your code executes.
So, for example, in a realistic application,
in your code you have lots of if statements
and switch statements and loops and function calls,
and you wanna make sure that your test
covers all possible execution paths through that code.
Okay? To make sure that there isn't some bug
lurkin' somewhere that you haven't quite discovered yet.
So, test coverage turns out to be a really important topic
in industrial strength testing.
So, we're gonna look at how to generate
a test coverage report using Jest.
The example that we're going to look at
is in the test coverage folder,
so JsTdd lesson three, test coverage,
and if you look in there, you'll find,
first of all, there are two source files.
We'll have a look at the source file
on the right, first of all.
It's to do with exam marks, so it's a very simple scenario,
but there's quite a lot of different execution paths
in that function and in that function.
So, first of all isValid.
The idea is that it receives an exam mark,
and the first thing it does is to check if it's too low.
If it's less than zero, it's an invalid mark. It's too low.
So, it returns a string to indicate the status.
No, that's not a valid mark. It's too low.
Maybe the mark is too high or maybe it's okay.
So, we have three possible execution paths
through that function.
We export that function from here,
and then we import it over here.
So, getGrade is like a higher level function.
It receives the exam mark,
and the first thing it does is to validate it.
It calls the isValid function from over here
and hopefully it's okay.
It checks is the status not okay?
In which case it returns the error message.
Okay? Like, "Too low" or "Too high."
Assumin' that the value is okay, then we proceed,
and the way it works in this example,
if your exam mark is greater than or equal to 70,
you get an A grade.
The purpose of this function is to tell you
what grade you've got for a given mark.
If your mark is between 60 and 69,
then you get a B, 50 to 59 is a C,
and below 50 is a fail. Okay?
So, you can see there are various different execution paths
through this function as well.
Okay. Right. Now, here are my tests.
I've got a test suite, which tests...
well, I think it's done a decent job.
I'm checkin' that if I get over 70,
it should gimme an A grade.
Exactly 70 should also be an A grade.
60 to 69 should give me a B,
50 to 59 should give me a C.
So, do these tests cover all execution paths?
Let's actually have a look at the proper code
before we answer that question.
In my text editor, here's my example code.
Well, first of all, here's my utility code,
here's my isValid function that tells me,
you know, is it even a valid mark?
And then my higher order function, in example.js,
is where I have my getGrade function
call the isValid function, first of all,
determine if it's not valid.
If it is valid, then tell me what grade I've got.
Okay? And then these are my tests.
So, this suite of tests is deliberately insufficient.
It doesn't test for invalid marks like -1 or 101,
and it doesn't check either,
it doesn't test either if you got less than 50.
So, when I wrote these tests,
maybe I didn't realize that I've missed
some paths of execution through the code.
Have a look. This first test function is testin' 75.
That's an A, then it's testin' 70.
Well, that should also be an A.
65 should be a B and so should 60.
55 should be a C and so should 50.
But notice that it's not checkin' for an out of range value.
My tests are insufficient.
It's not checkin' for a too low value,
a too high value or for a fail.
How can I get Jest to help me realize
that I've missed some execution paths?
Well, that's where test coverage comes into play.
With Jest, you can run a test in coverage mode.
You set the coverage option to be true.
It's --coverage=true.
Okay? And when you do that, it'll run the tests,
but it'll also give you back a visual report
to indicate any paths that you might have forgotten.
So, let's run that test and see
what the error report looks like.
So, "npm run test" and then options.
So, the option is --coverage=true.
So, run the tests and give me a coverage report
to let me know about any paths that I've missed.
So, we just have to wait for this to complete its task.
So, it's running through my five test functions.
They all worked, or there were six tests here.
They all worked, but I get this coverage report.
File by file, it'll tell me...
So, for example, in util.js,
it executed two thirds of the statements
and half of the branches.
Now, that's not so good, is it?
It executed all the functions in that file.
Well, there was only one function,
but only 66%, two thirds of the lines.
This is the useful thing here.
It tells me which lines in that file
weren't executed through my test path.
Line three and line five weren't covered by my tests.
Okay? So, line three and line five were skipped,
weren't executed by my tests in util.js.
Oh, well, let's have a look at util.js.
In util.js, it says line three was never-
my tests never took me down that path.
Okay, so that tells me my testin' is insufficient
because I haven't passed in a test
that would execute that path.
I would need to add another test into my test suite
where I passed in a value of -1.
And what was the other line that was a problem?
Oh, line five was also not covered.
Line five is this one, okay?
So, I haven't gone through this branch either.
I haven't given it a value like 101.
I would need to go back to my test suite
and add those functions into my test suite.
Okay. What about the other one then, example.js?
So, that had 75% of the branches covered,
but line seven wasn't covered, neither was line 17.
So, let's have a look at those and see why.
So, in example.js, line seven,
my tests never took it down that path.
Well, yes, that makes sense, doesn't it?
Because in my examples, I just gave it valid exam marks,
75, 70, 65, 60, 55, 50,
but I never gave it- I never tested
an invalid mark, like -1,
so my tests never took it down that path.
Right. So, line seven was wasn't covered.
Neither was line 17. Line 17...
Ah. Look! My tests didn't take me down the "fail" route.
I didn't have a test, for example, with a value like 45.
This is really useful.
There's no way, otherwise,
we'd know that our tests haven't
been executed thoroughly enough
or that our tests don't cover all the execution paths.
This would tell me what tests I need to add
to take my tests down these extra paths.
So, it's really nice.
When you run in coverage mode,
you get this visual appearance that you can see.
You can also generate a report.
So, you can see with the coverage option,
it's generated a folder called "coverage,"
and in there, there's a file called "coverage-final.json."
It's summary information like we've just seen,
but in JSON format that you can use
for automatin' your procedures, for example.
Rather than just having to look at the screen,
you can write some code that looks at this JSON format
and does somethin' automated.
So, my test coverage folder,
it generated the coverage sub-folder,
and in there I have "coverage-final.json."
Let's have a look.
So, a JSON format file,
which gives me a summary of my test run.
Okay? So, here we go.
If you scroll through, it gives you complete information
about what tests were executed
and what tests were not executed.
There's a lot of information in here.
Woof, look at that.
Okay, so it is all there.
That's the definitive summary of what happened.
Not the easiest thing to read, but it is there.
That's the complete snapshot of how the tests actually went.
There's also a clover.xml,
which displays summary information in XML format.
Clover.xml. So, if I open that one up in a text editor...
I was tempted to open it up in a browser window there,
but you can just open up the XML file in a code editor.
And here, let me just give myself a bit more space,
you can see the files that were tested.
So, that was the example.js file,
and it kind of tells you what line was executed,
and you can kind of see here as well that,
for the other file, util.js,
you can see here the metrics
of how many statements there were, how many were covered,
how many conditionals there were, how many you covered,
how many methods were covered,
and how many were there in total.
Okay, and for each line, it can tell you,
you know, how many times it went down the if branch
and what the results were.
So, there's full information here if you want to dig in,
or if you prefer, you can just use the visual feedback
as an indicator.
That's probably enough, initially,
to give you an idea about where you need to add tests.
What you then do is add tests back in
and then run it in coverage mode again,
and, hopefully, this time,
you'll find that everything's been covered properly
and your test is complete, and then you can have a rest.