What will we learn today?
- Tracing Code
- Unit Testing
- Unit Testing Frameworks - JEST
- Test-driven Development
- More on Testing
Fork and Clone the js-exercises-tdd repo
What is Code? Computer code is a set of rules or instructions. It is made up of words and numbers and when you put them in the right order it will tell your computer what you want it to do.
Let's trace these code samples together:
Testing our code
We have just traced the output of some code on paper, but how do ensure that the code actually does what it is supposed to do when we run it.
Discussion: How have we been testing our code so far? How do ensure it is correct. What is "correct" anyhow?
In many organisations, there are full teams dedicated to testing and ensuring that the code written behaves correctly, to report bugs and make sure that they are fixed on time. In general, Quality Assurance is a responsibility of everyone in a team starting from Project Manager, Scrum Masters, Developers and Testers.
There are typically several levels of testing when working on a project:
- Unit testing
- Integration testing
- Functional, End to End testing and User Acceptance Testing (UAT)
This answer from Stack Overflow has a good explanation of types of testing. The defintions for Functional, e2e and UAT are often mean different things in different teams, the responisibility for them also falls on different individuals depending on the team.
Unit testing though is always the responsibility of the Developer, and it is a very important skill for any professional developer to be able to write tests, and also write code that is testable.
Discussion: What is testable code?
Testing is a key skill for any software programmer. We need to make sure our software is thoroughly tested, otherwise bad things happen. Testing makes sure our programs behave like we intend them to do - if we don't test, we can cause severe bugs. Bad software can make planes crash, companies bankrupt, and users of your software really frustrated.
There are different levels on which we can test software, for example integration testing, end-to-end testing, and unit testing. Today we will deal with unit testing, which is probably the most universal testing discipline.
Remember when we talked about functions? Functions take input (as arguments),
do something with it (in the function body), and return output (using the
return statement). Ideally, a function should always return the same output if
the same input is given. It makes it predictable and testable - and that's what
So, when unit testing a function, we want to make sure that for a certain input, we get the expected output. For this we need to make sure that the output matches our expectations. In the simplest form that means we do an equality check:
We can formalise this using another function that compares two values and
complains when they do not match. Such a function is prepared in
We can use this function to simply compare to values:
Now we can use this
equals() function to test our own code by comparing a
function result to an expected value.
Remember that one function can be used as an argument when a second function is called. In this instance, the function we are testing would represent our first function, and our
equals() function would represent the second, like so...
As you can see in this example, instead of using a number as the first argument to the
equals() function, we have used a function instead; the one we wish to test.
Exercise: Write tests for the the exercises under
Unit testing frameworks
There are lots of other things you might want to test for than two things being equal. You might want to test if a number is smaller or greater than another, if a function was called, if an error happened, or if one thing happened before another thing, or how long a function call took to execute.
We don't have to build all these things ourselves. Instead there are unit testing frameworks that take all that work off our shoulders. All we need to do is provide the code and the tests.
The unit testing framework we are trying to day is called Jest. It's created by Facebook and useful for all kinds of unit testing (especially testing React, which we will do in a later lesson).
Look into your
jest/ folder. You will find a file there,
.test.js tells Jest that this file contains tests it should execute. To
execute the test, run the following command in your terminal:
This command runs the test in
sum.test.js, which tests the
You can see the test output and the fact that the test passed.
Tests cases in Jest have the following structure:
Jest provides a set of functions that you can use to write your actual tests. They are created in a way that imitates natural language, for example:
You can add multiple test statements in the same test case (a test case is one
call of the
test function, but you can also create multiple test cases in one
file. It is important that you give all your test cases meaningful descriptions.
Exercise: Add another test case to
sum.test.js. Is the sum of 10 and -10 really zero? Run the tests using Jest.
Exercise: Take the
findNeedlefunction you have tested previously, copy it into the
jest/folder and call it
findNeedle.test.js. Then write a test to be used with Jest, similar to
sum.test.js. Make sure you cover multiple inputs and give all tests meaningful descriptions! Run the tests using Jest.
Test Driven Development
Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the software is improved to pass the new tests, only. This is opposed to software development that allows software to be added that is not proven to meet requirements. [Wikipedia]
When developing following TDD, you normally follow this sequence:
- Add a test
- Run all tests and see if the new test fails (Red)
- Write the simplest code to make the test pass (Green)
Read more on the Wikipedia article and the resources at the end.
Exercise: Two mentors pair on a problem doing "ping pong" TDD. One writing the test, the other writing the implementation.
More on Testing
Test coverage describes the extent to which a code base is tested. When Jest runs your tests, it generates a so-called coverage report. This report tells you how many of your lines of code are covered by tests, how many functions, statements, and branches.
A branch is one of multiple ways a code control flow can go. For example, if you have an
if() ... else ..., both the "if" and the "else" branch must be covered by tests.
We want to keep our code coverage as high as possible. Jest allows us to generate a coverage report when we run the following command in the terminal:
Exercise: Check your code coverage for the tests you wrote. Is any of the numbers below 100%? If so, try and bring it up to 100%!
There are times when we want to make our code better without changing any functionality, for example because we just learnt about a better way to solve a certain problem (like, finding needles in haystacks). This is called refactoring.
When previously GREEN code - working code! - suddenly does not work anymore, we call this a regression. Our existing tests can make sure that when we refactor, the functionality of our code actually stays the same, and does not regress.
Exercise: Refactor some of the exercise we've written tests for.
So far, all our programs have been in their own single files. But Node programs can become really large, and having all our code in only one file will not be maintainable.
Creating modules, exporting code
The key here is the line containing
module.exports. As you see, this is an
assignment, and whatever is assigned to
module.exports will be made available
to other modules and program when this file is imported.
Using modules, importing code
But how do we make use of another module in our program? We need to import it,
and this is done using a function called
The string passed to the
require()function is a path to the file you are importing.
./signifies the current directory, so the above command will import a file called "printName.js" that is in the same directory as our program.
Assuming our program is in the same folder as
printName.js, we can use the
above code to import the functionality provided by that module and store it in
We can then continue to use the
printName function as if it we defined it in
our own program!
Modules can not only export functions, but all variable types you already learned about. Most commonly, they export a function or an object containing multiple functions.
Together: Edit the file
modules/main.jsand follow the instructions.
Separating code and tests
Exporting and importing modules is really useful for testing, too.
As a rule of thumb, we never want to mix our actual code with our tests. It is
therefore common to put them in separate files. We are going to call the file
containing the tests after the file containing the code to be tested, just
.test at the end of the filename. Like so:
The naming is really up to convention - you can even put your tests in a different folder! However, for Jest it is important to call test files "*.test.js".
You should know these terms by the end of this class: Testing, Quality Assurance, Unit Tests, Integration Tests, Refactoring, Regression Tests .. any more?
Tracing code resources
Finish the Katas and exercises in the project we've worked on today
Follow this tutorial about Debugging with Chrome
- Research other module formats than CommonJS. What is AMD? What are ES6 modules and how do their differ from CommonJS?
- More parts of the Jest (Jasmine) DSL than just