Language Settings

Unit Testing and Test Driven Development

This tutorial was written by Andy Timmons.

Overview

This tutorial will walk you through setting up your development environment to run unit tests against your code. Unit testing is a method of applying tests to the classes and functions in your code. It can catch bugs, help you collaborate with others, and code faster. Test driven development is a method of writing your tests before you write code so you have all of your tests when you finish.

This tutorial is intended for people who:

Why unit test?

In late 1998 NASA launched the Mars Climate Orbiter in collaboration with Lockheed Martin. This robotic space probe cost 193 million dollars and took one year to arrive and establish its orbit around Mars. Minutes later it crashed into Mars and exploded. NASA later determined it was a software error. The Lockheed Martin navigation code gave measurements in English units like feet, and the NASA code expected the measurements to be in metric units like meters. The difference between the two caused the crash. [Citation]

Unit testing could have prevented this error and while you might not be launching spaceships (but by all means please try), unit testing can prevent a lot of headaches from the outset of your project to install day. It can help you:

  • improve your design and reduce bugs
  • collaborate with others
  • finish your project faster

Setting up for unit testing

Installing node:

  1. First you must install node. If you already have node installed this assumes you are using version 4 or higher. Node is a open source runtime environment for building server side applications. Don’t worry if that sentence didn’t make sense. You don’t have to understand all there is to know about node to do this tutorial. If you are curious to learn more you can read the p5.js node tutorial.
  2. On Mac, Linux, Windows: Go to https://nodejs.org/en/ and download the installer. Open the installer and follow the directions.
  3. Once the installer finishes open a terminal and run the following command to verify it installed correctly. You should see something like "v6.6.0" or some other numbers that indicate the version you installed.
    
                node -v
                

Update npm, the Node Package Manager. Npm is a program that allows you to install software libraries that are compatible with node. In your open terminal run the command:


          sudo npm install npm@latest -g
        

Use npm to install the chai assertion library. This is what you will use to build your tests. In the terminal window run the command:


          npm install chai -g
        

Use npm to install the mocha test running library. Mocha drives your chai tests. Much like a person drives a car. In the terminal window run:


          npm install mocha -g
        

A sketch without tests

Your goal today is to make a sketch with a square whose fill color loops over every RGB color.

Let’s set up the project. This is what it would look like without any unit tests.

  1. Make a folder called color_unit_test and open it
  2. Make a file called index.html and type the following code. While you can just copy/paste it, typing it out will help you understand what it is doing better. It is worth the time!
    
                  <!DOCTYPE html>
                  <html>
                  <head>
                    <meta charset="UTF-8">
                    <meta name="viewport" content="width=device-width, initial-scale=1">
                    <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.js"></script>
                    <script src="sketch.js"></script>
                    <style> body {padding: 0; margin: 0;} </style>
                  </head>
                  <body>
                  </body>
                  </html>
                
  3. make a file called sketch.js and put in the following code
    
                  // This is for a unit test tutorial.
                  // it should create a rectangle and allow you to iterate over
                  // every single color.
                  //
                  // colorValueIncrease sets the amount the color changes on each
                  // draw loop. Values greater than 255 will break the sketch.
                  // fillColor will be the color of the rectangle.
                  // colorIncreaser will become an instance of our ColorIncreaser class.
    
                  let colorValueIncrease = 1;
                  let fillColor;
                  let colorIncreaser;
    
                  function setup() {
                    createCanvas(500, 500);
                    background(0);
                    fillColor = color(0, 0, 0, 255);
                    noStroke();
                  }
    
                  function draw() {
                    fill(fillColor);
                    rect(0, 0, 500, 500);
    
                    // increment the red value
                    fillColor.levels[0] += colorValueIncrease;
                    // If the red value is maxed out increment the green value
                    // and reset the red value.
                    if (fillColor.levels[0] > 255) {
                      fillColor.levels[0] = 0;
                      fillColor.levels[1] += colorValueIncrease;
                    }
                    // If the green value is maxed out increment the blue value
                    // and reset the green value.
                    if (fillColor.levels[1] > 255) {
                      fillColor.levels[1] = 0;
                      fillColor.levels[2] += colorValueIncrease;
                    }
                    // If the blue value is maxed out reset the green value.
                    if (fillColor.levels[2] > 255) {
                      fillColor.levels[2] = 0;
                    }
                  }
                
  4. Open the index.html file in your browser (you don’t need to run a local server in this case). You should see a black box become red and then black again. This is the basis of looping over all the RGB colors. The problem you face is that there are 256 values for red, green, and blue, which means there are over 16 million combinations of colors! You can make this faster. However, making this faster will cause flashing lights which might give some people headaches or seizures. Please only increase this value if you do not have issues with flashing lights. The rest of the tutorial should be fine. In the sketch.js file change the
    let colorValueIncrease = 33;
    and reload the page. However this still doesn’t guarantee this is working properly. You don’t have time to watch all the colors change nor can you tell yourself if it is iterating through the millions of colors. Change the colorValueIncrease variable back to 1 before you move on.
  5. At 30 draw loops per second it would take over six days to loop through all of the colors. You certainly don’t have time to do that once, and would never be able to check and see if it worked every time you updated our code. What if you accidentally change something in our draw loop that breaks the code right before install? You wouldn’t know until it was live and everyone saw your sketch break.

Your first unit tests

Unit testing can help us out here. The majority of the time spent in this program is the actual drawing of the pixels. Adding 1 and checking a few if statements is very fast if you don’t need to draw the actual pixels. So you can check if the color values increase as expected without having to actually watch them all increase on your screen.

In the folder called color_unit_test that you already created, make a new folder called test. Open the test folder and make a file called test.js. Type this code into test.js


          'use strict';

          // Import the expect library.  This is what allows us to check our code.
          // You can check out the full documentation at http://chaijs.com/api/bdd/
          const expect = require('chai').expect;


          // Create the variable you are going to test
          let p5js = 'awesome';


          // describe lets you comment on what this block of code is for.
          describe('these are my first tests for p5js', function() {


            // it() lets you comment on what an individual test is about.
            it('should be a string', function(done) {
              // expect is the actual test.  This test checks if the var is a string.
              expect(p5js).to.be.a('string');
              // done tells the program the test is complete.
              done();
            });


            it('should be equal to awesome', function(done) {
              // This expect tests the value of the string.
              expect(p5js).to.equal('awesome');
              done();
            });
          });
        

Save test.js and go back to your command line and cd into the main directory of our sketch called color_unit_test. Type the command

mocha

into the command line and watch your first test run! You should see something like:


          these are my first tests for p5js
              ✓ should be a string
              ✓ should be equal to awesome
            2 passing (14ms)
        

Let's look at what happens when tests fail. Change line 6 in test.js from:

let p5js = 'awesome';

to

let p5js = 42;

Save test.js and go back to your terminal and run the mocha command again. You should see something like:


          these are my first tests for p5js
              1) should be a string
              2) should be equal to awesome

            0 passing (18ms)
            2 failing

            1) these are my first tests for p5js should be a string:
               AssertionError: expected 42 to be a string
                at Context. (test/test.js:14:24)

            2) these are my first tests for p5js should be equal to awesome:
               AssertionError: expected 42 to equal 'awesome'
                at Context. (test/test.js:21:21)
        

What is nice about these errors is:

  • They tell you the name of the test that you put in the describe() and it() functions
  • They tell you what they expected with AssertionError: expected 42 to be a string
  • They tell you what file and line of code had the problem test/test.js:14:24

Now that you know the basics of unit testing you are ready to write some tests against your code and refactor your sketch!

Test Driven Development

You are going to use Test Driven Development for the rest of this tutorial. The idea is you write your unit tests first, run them and watch them fail, and then add code to sketch.js to make the tests pass. This way you ensure your code is working as you expect every step of the way and that new code doesn’t break old code. Your goal is to create a ColorIncreaser class that:

  • takes in an integer as the value to increase each time called colorValueIncrease.
  • takes in an instance of the p5 color class.
  • has a function to increase the values in the color by the value in colorValueIncreases.

Delete everything in test.js and replace it with this


          'use strict';

          // Import the expect library.  This is what allows us to check our code.
          // You can check out the full documentation at http://chaijs.com/api/bdd/
          const expect = require('chai').expect;
          // Import our ColorIncreaser class.
          const ColorIncreaser = require('../sketch');

          describe('ColorIncreaser tests', function() {
            // Will hold the reference to the ColorIncreaser class
            let colorIncreaser;

            // beforeEach is a special function that is similar to the setup function in
            // p5.js.  The major difference it that this function runs before each it()
            // test you create instead of running just once before the draw loop
            // beforeEach lets you setup the objects you want to test in an easy fashion.
            beforeEach(function() {
                colorIncreaser = new ColorIncreaser();
            });

            it('should be an object', function(done) {
              expect(colorIncreaser).to.be.a('object');
              done();
            });
          });
        

Now run the mocha command in your terminal and watch your new test fail! The crux of the error is “TypeError: ColorIncreaser is not a constructor”. This makes sense because you have not created a ColorIncreaser class in our sketch yet.
Go to the bottom of your sketch.js file, below the closing bracket } for the draw function, and add this:


          class ColorIncreaser {
            constructor() {
              // Stores a value and a color and allows you to increase the color
              // by that value.
            }
          }

          module.exports = ColorIncreaser;
        

Save sketch.js. The function line will be our color increasing object. The module.exports line is what allows our test.js file to import our ColorIncreaser with the line you already added that looks like this:


           const ColorIncreaser = require('../sketch');
        

If you go back to your terminal and run mocha your test should pass.
Now get your ColorIncreaser function to actually do something. Start by storing the colorValueIncrease as a variable in your class. Before you change the code in sketch.js you have to write your tests. Change the beforeEach function in test.js to look like this


          beforeEach(function() {
              let colorValueIncrease = 1;
              colorIncreaser = new ColorIncreaser(colorValueIncrease);
          });
        

and add a new it() test below it like this


          it('should store initial values without mutation', function(done) {
            expect(colorIncreaser.colorValueIncrease).to.be.equal(1);
            done();
          });
        

Now run the mocha command and you will get the pivotal line:
AssertionError: expected undefined to equal 1
because you have not added this to your class yet. You need to add that value to get this to work. Add this line to the setup function in sketch.js under noStroke();


          colorIncreaser = new ColorIncreaser(colorValueIncrease);
        
and change the ColorIncreaser function to look like this

          class ColorIncreaser() {
            constructor(colorValueIncrease) {
              // Stores a value and a color and allows you to increase the color
              // by that value.
              this.colorValueIncrease = colorValueIncrease
            }
          }
        
Let’s run the mocha test again. It should pass!

Testing when your object is composed of other objects

Now you need to add an instance of the p5 color class to our ColorIncreaser. However, for your tests, you don’t want to use an actual instance of the color() class because you don’t want to test external dependencies given by another library.You just want to make sure that your increment function works. So you are going to create what is called a mock of the p5 color class so you can test without worrying about the implementation of code you didn’t write.

The original way of incrementing colors just used the color.levels and changed the red, green and blue values at index [0], [1], and [2]. You can see this in the draw function in sketch.js. The implementation of color just stores those values in a javascript array so you can mock it out very easily. Put this code in test.js below the const ColorIncreaser = require… line and above the describe line


          function mockColor(red, green, blue, alpha) {
              // Mock of the color class from p5
              this.levels = [];
              this.levels[0] = red;
              this.levels[1] = green;
              this.levels[2] = blue;
              this.levels[3] = alpha;
          }
        

Update your beforeEach() function


          beforeEach(function() {
              let colorValueIncrease = 1;
              let fillColor = new mockColor(0, 0, 0, 255);
              colorIncreaser = new ColorIncreaser(colorValueIncrease, fillColor);
          });
        

And update the it('should initialize without mutation', function block


          it('should store initial values without mutation', function(done) {
            expect(colorIncreaser.colorValueIncrease).to.be.equal(1);
            expect(colorIncreaser.fillColor.levels[0]).to.equal(0)
            expect(colorIncreaser.fillColor.levels[1]).to.equal(0)
            expect(colorIncreaser.fillColor.levels[2]).to.equal(0)
            expect(colorIncreaser.fillColor.levels[3]).to.equal(255)
            done();
          });
        

And run the tests! Failure! TypeError: Cannot read property 'levels' of undefined
You have to update the ColorIncreaser class to take in a fillColor variable. It is an easy fix. In sketch.js change the colorIncreaser line in setup() to look like this


          colorIncreaser = new ColorIncreaser(colorValueIncrease, fillColor);
        

and update the ColorValueIncreaser function


          class ColorIncreaser {
            constructor(colorValueIncrease, fillColor) {
              // Stores a value and a color and allows you to increase the color
              // by that value.
              this.colorValueIncrease = colorValueIncrease
              this.fillColor = fillColor;
            }
          }
        

Save sketch.js. Run the tests again and they should pass!

Now you have all the initialized variables you need to make the class work. This provides the scaffold on which you will build your method to increase the color value and iterate over every color.

Testing class methods

You are going to make a class method called increaseFillColor. It will basically run the code that is currently in our draw loop, but use the values stored in our ColorIncreaser class to do it.

As covered before there are 256 values for red, green and blue giving 16,777,216 color combinations. While that might seem overwhelming you can test it easily because you know what values should be present in red, green and blue after each time you call our increaseFillColor function. For example if you start with the color black with the rgb levels 0,0,0 and you call increaseFillColor once you know you should now have the values 1,0,0. So you are going to add tests in test.js for calling increaseFillColor 255, 65535 and 16777215 times.


          it('should have rgb values 255, 0, 0 after calling increaseFillColor 255 times', function(done) {
            //it is 256^1 - 1 because it starts with the color black
            for (let count = 0; count < 255; count += 1) {
                colorIncreaser.increaseFillColor()
            }
            expect(colorIncreaser.fillColor.levels[0]).to.equal(255)
            expect(colorIncreaser.fillColor.levels[1]).to.equal(0)
            expect(colorIncreaser.fillColor.levels[2]).to.equal(0)
            done();
          });


          it('should have rgb values 255, 255, 0 after calling increaseFillColor 65535 times', function(done) {
            //it is 256^2 - 1 because it starts with the color black
            for (let count = 0; count < 65535; count += 1) {
                colorIncreaser.increaseFillColor()
            }
            expect(colorIncreaser.fillColor.levels[0]).to.equal(255)
            expect(colorIncreaser.fillColor.levels[1]).to.equal(255)
            expect(colorIncreaser.fillColor.levels[2]).to.equal(0)
            done();
          });


          it('should have rgb values 255, 255, 255 after calling increaseFillColor 16777215 times', function(done) {
            //it is 256^3 - 1 because it starts with the color black
            for (let count = 0; count < 16777215; count += 1) {
                colorIncreaser.increaseFillColor()
            }
            expect(colorIncreaser.fillColor.levels[0]).to.equal(255)
            expect(colorIncreaser.fillColor.levels[1]).to.equal(255)
            expect(colorIncreaser.fillColor.levels[2]).to.equal(255)
            done();
          });
        

And when you run mocha the tests will fail because that function does not exist! Let’s add a skeleton function in sketch.js inside the ColorIncreaser class below the constructor function.


          increaseFillColor() {
            // increase the first color channel by one.  If that channel
            // is now > 255 then increment the next color channel.  Repeat for second
            // and third channel
          }
        

Save sketch.js. Now when you run mocha the tests fail for a different reason. It finds the function, but it doesn’t do what you expect it to. Let’s change the function in sketch.js to look like what is inside the draw function.


          increaseFillColor() {
            // increase the first color channel by one.  If that channel
            // is now > 255 then increment the next color channel.  Repeat for second
            // and third channel

            this.fillColor.levels[0] += this.colorValueIncrease
            this.numColorsSoFar += 1


            if (this.fillColor.levels[0] > 255) {
              this.fillColor.levels[0] = 0
              this.fillColor.levels[1] += this.colorValueIncrease
            }
            if (this.fillColor.levels[1] > 255) {
              this.fillColor.levels[1] = 0
              this.fillColor.levels[2] += this.colorValueIncrease
            }
            if (this.fillColor.levels[2] > 255) {
              this.fillColor.levels[2] = 0;
            }
          }
        

And now your tests pass! Hooray!
Now you just need to get the draw function set up. Replace the draw function in sketch.js with this:


          function draw() {
            fill(colorIncreaser.fillColor);
            rect(0, 0, 500, 500);
            colorIncreaser.increaseFillColor()
          }
        

All tests should pass now and you can expect your code to behave as you want! As a bonus when you change the code in the future the tests will let you know that it still works as intended!
Congratulations, you made it to the end! Unit tests and Test Driven Development are powerful ways to build code that you know works just as you expect.
You can continue to build on your skills by