• Stars
    star
    2,698
  • Rank 16,202 (Top 0.4 %)
  • Language
    JavaScript
  • License
    Mozilla Public Li...
  • Created almost 9 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

βœ… A brief introduction to Test Driven Development (TDD) in JavaScript (Complete Beginner's Step-by-Step Tutorial)

Learn Test Driven Development (TDD)

A brief introduction to Test Driven Development (TDD) in JavaScript for people who want to write more reliable code.

GitHub Workflow Status codecov.io Dependencies: None contributions welcome HitCount

Why?

Project(s) without tests often end up looking like they are stuck together with duct tape ...

duct tape car fail

Change one part and another stops working? "Fixing" one bug, creates another?

Wouldn't you prefer it if everything was consistent and beautifully integrated?
What if everyone on your team worked like clock-work in a disciplined order... like a Formula 1 Crew ...

formula 1 pit stop

Test Driven Development (TDD) makes your team a well-oiled machine which means you can go faster.

Once you have a suite of tests that run on every change, you will begin to develop a whole other level of confidence in your codebase and will discover a new freedom to be creative without fear of "breaking" anything unexpectedly; truly game-changing.

What?

This tutorial will help you get started with Test Driven Development (TDD) today!
In the next 30 minutes you will learn everything1 you need to write tests for your web project!

Pre-Requisites

  • A computer with a web browser
  • Internet access to download the starter files
  • 30 minutes of your time
  • Basic Programming Skills (HTML & JavaScript)
  • (Optional) Bonus Levels requires you to install Node.js

What is Software Testing?

Software testing is the process of evaluating a software item to detect differences between the expected output and the actual output. Testing assesses the quality of the product. Software testing is a process that should be done during the development process. In other words software testing is a verification and validation process.

What is TDD?

Test-driven development (TDD) is an evolutionary approach to development which combines test-first development, where you write a test before you write just enough production code to fulfil that test, and refactoring. In other words, it’s one way to think through your requirements or design before you write your functional code.

From Introduction to Test Driven Development (TDD)

Further resources

How?

The first thing you need to understand is that writing code following TDD (discipline) is a (slightly) different approach from simply diving into solving the problem (without a test).

When reading about TDD you will usually see the expression: "Red, Green, Refactor":

TDD Cycle: Red, Green, Refactor

What this means is that TDD follows a 3-step process:

  1. Write a Failing Test - Understand the (user) requirements/story well enough to write a test for what you expect. (the test should fail initially - hence it being "Red")

  2. Make the (failing) Test Pass - Write (only) the code you need to make the (failing) test pass, while ensuring your existing/previous tests all still pass (no regressions).

  3. Refactor the code you wrote take the time to tidy up the code you wrote to make it simpler (for your future self or colleagues to understand) before you need to ship the current feature, do it.

Thankfully, because you will have good tests, you don't need to do any refactoring up-front, you can always do refactoring later if performance bottlenecks are discovered. Most programming languages have very efficient compilers/interpreters that remove much of the need for refactoring. And if you use a linter your code will be naturally "tidy".

To develop the habit(s) you will need to be successful with TDD (and software engineering in general) we need to write a test first (and watch it fail) and then write the code required to make the test pass.

Writing a failing test, before writing the code may seem counter-intuitive, time consuming or even "tedious" at first. But we urge you to think of it this way:

The test is the question you are asking
your code is the answer to the question.
By having a clear question, you can always check that your code is working,
because it consistently gives you the same answer(s) ... no surprises, even when you're working with a large, inter-dependent code base!

Practical

Note: This tutorial is meant to be a beginner-friendly introduction to TDD. The Vending Machine example is intentionally simple so you can focus on the principles of testing. Once you understand the basics, we encourage you to follow our complete Todo List Tutorial (https://github.com/dwyl/todo-list-javascript-tutorial), which is a step-by-step guide to building an App following testing and documentation-first best practices.

Scenario: Vending Machine Change Calculator Micro-Project

vending machine

Imagine you are building a Vending Machine that allows people to buy any item it contains. The machine accepts coins and calculates the change to be returned to the customer, given the item price and the cash received.

Single File App

We can build the entire "project" in a single file: index.html

Note: In practice you want to split your JavaScript, CSS and HTML (Templates) into separate files, but for this example we are keeping everything in index.html for simplicity. If you make it to the "Bonus Levels" you will split things out!

Create a directory on your computer called vending-machine:

In your terminal type this command:

mkdir vending-machine && cd vending-machine

(This will create the directory and move you into it)

Next create a file called index.html e.g: atom index.html (which creates and opens the file in the Atom text editor if you have it installed)

(Note: The "atom" command is not installed by default. In the Atom menu bar there is a command named β€œInstall Shell Commands” which installs a new command in your Terminal called "atom".)

Now copy-paste the following sample code into the newly created index.html file to get started:

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Vending Machine Change Calculator TDD Tutorial</title>
    <!-- Load the QUnit CSS file from CDN - Require to display our tests attractively -->
    <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-1.18.0.css">
    <!-- Pure CSS is a minimalist CSS file we have included to make things look nicer -->
    <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">
  </head>
  <body style='margin: 0 1em;'>
    <div id='main'>
      <h1>Vending Machine <em>Change Calculator</em></h1>
      <h2>Calculate the change (<em>coins</em>) to return to a customer when they buy something.</h2>
    </div>

    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
    <!-- Load the QUnit Testing Framework from CDN - this is the important bit ... -->
    <script src="https://code.jquery.com/qunit/qunit-1.18.0.js"></script>
    <script>
      // This is what a simple unit test looks like:
      test('This sample test should always pass!', function(assert) {
        var result = 1 + 1;
        assert.equal(result, 2); // just so we know everything loaded ok
      });
      // A failing test will be RED:
      test('This is what a failing test looks like!', function(assert) {
        var result = [1,2,3].indexOf(1);  // this should be 0
        assert.equal(result, -1); // we *expect* this to fail
      });
    </script>
  </body>
</html>

Open index.html in your Browser

When you open index.html in your web browser you should expect to see something like this: (without the annotation pointing out the qunit div, and the green and red annotations pointing out the Passing and Failing tests)

learn-tdd-initial-index-html-showing-failing-test

Explanation

There is quite a lot of code in the index.html you just created, let's step through it to understand the parts:

The first part of index.html is a standard HTML head and body:

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Vending Machine Change Calculator TDD</title>
    <!-- Load the QUnit CSS file from CDN - Require to display our tests attractively -->
    <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-1.18.0.css">
    <!-- Pure CSS is a minimalist CSS file we have included to make things look nicer -->
    <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">
  </head>
  <body>
    <div id='main'>
      <h1>Vending Machine Change Calculator</h1>
      <h2>Calculate the Change for a Given Price and Cash Received</h2>
    </div>

Nothing special here, we are simply setting up the page and loading the CSS files.

Next we see the qunit divs (where the test results will be displayed) and load the JQuery and QUnit Libraries from CDN:

    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
    <!-- Load the QUnit Library from CDN - this is the important bit ... -->
    <script src="https://code.jquery.com/qunit/qunit-1.18.0.js"></script>

Finally we see our test(s) - the interesting part of the file:

    <script>
      // This is what a simple unit test looks like:
      test('This sample test should always pass!', function(assert) {
        var result = 1 + 1;
        assert.equal(result, 2);
      });

      // A failing test will be RED:
      test('This is what a failing test looks like!', function(assert) {
        var result = [1,2,3].indexOf(1);  // this should be 0
        assert.equal(result, -1); // we *expect* this to fail
      });

    </script>
  </body>
</html>

If you are new to writing automated tests, don't worry - they are really simple. There are 3 parts:

  1. Description - usually the first parameter to QUnit's test() method, describing what is expected to happen in the test
  2. Computation - executes a function/method (which invokes the method you will write to make your test pass)
  3. Assertion - verifies that the result of your computation is what you expect it to be.

anatomy-of-a-unit-test

In the above screenshot, the assertion is assert.equal(result, 2)
We are giving the equal method two arguments; the result of our computation and our expected value - in this case 2. That's it.

Note: The latest version of QUnit uses the QUnit.test() function to run tests. Later in this workshop we use blanket.js which is not compatible with the latest version of QUnit. It is for this reason that we are calling test() to run the tests in this workshop.

Further Reading:

Requirements

As a customer, I want to buy a selected item from the vending machine and see what my change is as a result into the various coins so that I can select one of the options and receive my change.

Acceptance criteria:

  • A successful call of a function getChange should return the change value in the various coins available
  • Unit Tests should exist when the function is ready
  • The selection of the desired return is out of scope
Complementary User Story view

Given a Price and an amount of Cash from the Customer Return: Change to the customer (in notes and coins).

Understand what is needed

  • Create a function called getChange that accepts two parameters: totalPayable and cashPaid
  • For a given totalPayable (the total amount an item in the vending machine costs) and cashPaid (the amount of cash the customer paid into the vending machine), getChange should calculate the change the machine should return to the customer
  • getChange should return change as an array of coins (largest to smallest) that the vending machine will need to dispense to the customer.

Example

If a customer buys an item costing Β£2.15 (we represent this as 215 pennies totalPayable) and pays Β£3 (3 x Β£1 or 300 pennies cashPaid) into the vending machine, the change will be 85p.
To dispense the 85p of change we should return four coins to the person: 50p, 20p, 10p and 5p.
An array of these coins would look like: [50, 20, 10, 5]

Coins

In the UK we have the following Coins:

GBP Coins

If we use the penny as the unit (i.e. 100 pennies in a pound) the coins can be represented as:

  • 200 (Β£2)
  • 100 (Β£1)
  • 50 (50p)
  • 20 (20p)
  • 10 (10p)
  • 5 (5p)
  • 2 (2p)
  • 1 (1p)

this can be stored as an Array:

var coins = [200, 100, 50, 20, 10, 5, 2, 1];

Note: The same can be done for any other cash system ($ Β₯ €) simply use the cent, sen or rin as the unit and scale up notes.

The First Test

If you are totally new to TDD I recommend reading this introductory article by Scott Ambler (especially the diagrams) otherwise this (test-fail-code-pass) process may seem strange ...

In Test First Development (TFD) we write a test first and then write the code that makes the test pass.

First Requirement

So, back in our index.html file remove the dummy tests and add the following lines:

test('getChange(1,1) should equal [] - an empty array', function(assert) {
  var result = getChange(1, 1); //no change/coins just an empty array
  var expected = [];
  assert.deepEqual(result, expected);
}); // use deepEqual for arrays see: https://api.qunitjs.com/deepEqual/

We use QUnit's deepEqual (assert) method to check that all the elements in the two arrays are identical. see: https://api.qunitjs.com/deepEqual/

At this point, your index.html file should look like this:

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Vending Machine Change Calculator TDD</title>
    <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-1.18.0.css">
    <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">
  </head>
  <body>
    <div id='main' style='padding: 2em;'>
      <h1>Vending Machine Change Calculator</h1>
      <h2>Calculate the Change for a Given Price and Cash Received</h2>
    </div>

    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
    <script src="https://code.jquery.com/qunit/qunit-1.18.0.js"></script>

    <script>
    // your solution will go here!
    </script>

    <script>
    test('getChange(1,1) should equal [] - an empty array', function(assert) {
      var result = getChange(1, 1); //no change/coins just an empty array
      var expected = [];
      assert.deepEqual(result, expected);
    }); // use deepEqual for arrays see: https://api.qunitjs.com/deepEqual/

    </script>
  </body>
</html>

Watch it Fail

Back in your browser window, refresh the browser and watch it fail:

first failing test

Q: Why deliberately write a test we know is going to fail...?
A: To get used to the idea of only writing the code required to pass the current (failing) test.
Read: "The Importance of Test Failure: https://www.sustainabletdd.com/2012/03/importance-of-test-failure.html
Note: This also proves the test will fail if the code doesn't behave as expected.

Create the getChange function

In your index.html file add the following code (above the tests)

<script>
function getChange (totalPayable, cashPaid) {
    var change = [];
    // your code goes here

    return change;
};
</script>

Your index.html should now look something like this:

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Vending Machine Change Calculator TDD</title>
    <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-1.18.0.css">
    <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">
  </head>
  <body>
    <div id='main' style='padding: 2em;'>
      <h1>Vending Machine Change Calculator</h1>
      <h2>Calculate the Change for a Given Price and Cash Received</h2>
      <!-- <input type='text' id='price'> </input> -->
    </div>

    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
    <script src="https://code.jquery.com/qunit/qunit-1.18.0.js"></script>

    <script>
    var getChange = function (totalPayable, cashPaid) {
        'use strict';

        var change = [];

        return change
    };
    </script>

    <script>
    test('getChange(1,1) should equal [] - an empty array', function(assert) {
      var result = getChange(1, 1); //no change/coins just an empty array
      var expected = [];
      assert.deepEqual(result, expected);
    }); // use deepEqual for arrays see: https://api.qunitjs.com/deepEqual/

    </script>
  </body>
</html>

Refresh index.html in the Browser

first test passes

It Passed!!

Now Let's Write A Real Test

Going back to the requirements, we need our getChange method to accept two arguments/parameters (totalPayable and cashPaid), and to return an array containing the coins equal to the difference between them:

e.g:

totalPayable = 215          // Β£2.15
cashPaid     = 300          // Β£3.00
difference   =  85          // 85p
change       = [50,20,10,5] // 50p, 20p, 10p, 5p

Add the following test to tests section of index.html:

test('getChange(215, 300) should return [50, 20, 10, 5]', function(assert) {
  var result = getChange(215, 300); // expect an array containing [50,20,10,5]
  var expected = [50, 20, 10, 5];
  assert.deepEqual(result, expected);
})

Write the Method to Pass the Test

What if I cheat and make getChange return the expected result?

function getChange (totalPayable, cashPaid) {
  'use strict';

  var change = [50, 20, 10, 5]; // just "enough to pass the failing test"

  return change;
};

This will pass the new test, but it also introduces a regression. The original test getChange(1,1) should equal [] - an empty array is now failing.

Step 2 of the TDD process requires that all tests should pass, not just the newly added one.

The getChange function needs to cater for two scenarios; when change should be returned and when it shouldn't. A new implementation of getChange that handles both scenarios could be:

function getChange (totalPayable, cashPaid) {
  'use strict';

  var change = [];

  if((cashPaid - totalPayable) != 0) { // Is any change required?
    change = [50, 20, 10, 5]; // just "enough to pass the failing test"
  }

  return change;
};

The regression has been fixed and all tests pass, but you have hard coded the result (not exactly useful for a calculator...)

This only works once. When the Spec (Test) Writer writes the next test, the method will need to be re-written to satisfy it.

Let's try it. Work out what you expect so you can write your test:

totalPayable = 486           // Β£4.86
cashPaid     = 600           // Β£6.00
difference   = 114           // Β£1.14
change       = [100,10,2,2]  // Β£1, 10p, 2p, 2p

Add the following test to index.html and refresh your browser:

test('getChange(486, 600) should equal [100, 10, 2, 2]', function(assert) {
  var result = getChange(486, 600);
  var expected = [100, 10, 2, 2];
  assert.deepEqual(result, expected);
})

Should We Keep Cheating or Solve the Problem?

We could keep cheating by writing a series of if statements:

function getChange (totalPayable, cashPaid) {
  'use strict';

  var change = [];

  if((cashPaid - totalPayable) != 0) { // Is any change required?
    if(totalPayable == 486 && cashPaid == 600)
        change = [100, 10, 2, 2];
    else if(totalPayable == 215 && cashPaid == 300)
        change = [50, 20, 10, 5];
  }

  return change;
};

The Arthur Andersen Approach gets results in the short run ...

But it's arguably more work than simply solving the problem. So let's do that instead.

Try It Yourself (before looking at the solution!)

Try to create your own getChange method that passes the three tests before you look at the solution...

To re-cap, these are our three tests:

test('getChange(1,1) should equal [] - an empty array', function(assert) {
  var result = getChange(1, 1); //no change/coins just an empty array
  var expected = [];
  assert.deepEqual(result, expected);
});

test('getChange(215, 300) should return [50, 20, 10, 5]', function(assert) {
  var result = getChange(215, 300); // expect an array containing [50,20,10,5]
  var expected = [50, 20, 10, 5];
  assert.deepEqual(result, expected);
});

test('getChange(486, 600) should equal [100, 10, 2, 2]', function(assert) {
  var result = getChange(486, 600);
  var expected = [100, 10, 2, 2];
  assert.deepEqual(result, expected);
});

One More Test to be Sure it Works?

Let's invent a test that will return one of each of the coins ...

Recall that we have 8 types of coins:

var coins = [200, 100, 50, 20, 10, 5, 2, 1];

The sum of the (array containing one of each) coins is: 388p

So, we need to create a test in which we pay Β£4 for an item costing 12p. (A bit unrealistic, but if it works we know our getChange method is ready!)

test('getChange(12, 400) should return [200, 100, 50, 20, 10, 5, 2, 1]', function(assert) {
  var result = getChange(12, 400);
  var expected = [200, 100, 50, 20, 10, 5, 2, 1];
  assert.deepEqual(result, expected);
});

When these tests pass, your work is done.



Solution(s) contributions welcome

Note: Feel free to suggest a more compact algorithm.

"Imperative" Version ("Two For Loops")

var coins = [200, 100, 50, 20, 10, 5, 2, 1]
function getChange (payable, paid) {
  var change = [];
  var length = coins.length;
  var remaining = paid - payable; // we reduce this below

  for (var i = 0; i < length; i++) { // loop through array of notes & coins:
    var coin = coins[i];

    var times_coin_fits = Math.floor(remaining / coin); // no partial coins
    if(times_coin_fits >= 1) { // check coin fits into the remaining amount

      for(var j = 0; j < times_coin_fits ; j++) { // add coin to change x times
        change.push(coin);
        remaining = remaining - coin;  // subtract coin from remaining
      }
    }
  }
  return change;
};

"Functional"

The "functional" solution is more compact than the "nested for loops":

const COINS = [200, 100, 50, 20, 10, 5, 2, 1]; // "constant" of all coins
function getChange (payable, paid) {
  return COINS.reduce((change, coin) => {
    const change_sum = change.reduce((sum, coin) => sum + coin, 0);
    const remaining = paid - payable - change_sum;
    const times_coin_fits = Math.floor(remaining / coin);
    return change.concat(Array(times_coin_fits).fill(coin));
  }, []); // change array starts out empty and gets filled iteratively.
}

Don't panic if you are unfamiliar with the JavaScript Array.Map & Array.Reduce methods; they were new to everyone once.

We recommend reading:

Alternative Solution

An alternative shared by @blunket:

var cointypes  = [200, 100, 50, 20, 10, 5, 2, 1];
function getChange(price, paid) {
  var difference = paid - price;
  var change = [];

  cointypes.forEach(function(coin) {
    // keep adding the current coin until it's more than the difference
    while (difference >= coin) {
      change.push(coin);
      difference = difference - coin;
    }
  });

  return change;
}

If you see this:

learn-tdd-showing-all-passing-tests

Congratulations! You can do Test Driven Development (TDD)!!

Give yourself a pat on the back! Tweet your success!
or Re-Tweet: https://twitter.com/livelifelively/status/768645514120212480 learn-tdd

Take a break, grab some water and come back for the #BonusLevel


- - -

Bonus Level 1: Code Coverage (10 mins)

What is Code Coverage?

sign not in use

In computer programming, code coverage is a measure used to describe the degree to which the source code of a program is tested by a particular test suite.

In other words: if there is code in the codebase which is not "covered" by a test, it could potentially be a source of bugs or undesirable behaviour.

Read more: https://en.wikipedia.org/wiki/Code_coverage

Example from our Vending Machine Coin Change Example

Imagine the makers of the Vending Machine (unknowingly) hired a rogue programmer to build the change calculator.

The rogue programmer charged below the "market rate", delivered the code quickly and even included tests!

The makers of the vending machine think that everything is working fine, all the tests pass and when they try the machine it dispenses the merchandise and the correct change every time.

But in the getChange method the rogue programmer put in the following lines:

if(cashPaid == 1337) {
  ATM = [20, 10, 5, 2];
  for(var i = 0; i< 18; i++) { ATM.push(100) };
  return ATM; }

If all the QA person did was run the tests they would see them all "green" and think the job was well done.

But ... once the vending machines had gone into service, e.g: one in every train station in the country. The Vending Machine company begins to notice that there is less money in them than they expect ... They don't understand why because they only hire trustworthy people to re-stock the machines.

One day the Vending Machine Company decide to hire you to review the code in the getChange calculator and you discover the rogue programmer trick!

Every time the rogue programmer inserts Β£13.37 into any vending machine it will payout Β£18.37 i.e: a Β£5 payout (and a "free" item from the vending machine!)

How could this have been prevented?

The answer is code coverage!

Note: Checking code coverage is not a substitute for QA/Code Review...!

Blanket.js

To check the coverage of code being executed (in the browser) we use Blanket.js

See: https://blanketjs.org/ and https://github.com/alex-seville/blanket

To run blanket.js we need to separate our tests and solution into distinct .js files:

test.js contains our unit tests

test('getChange(1,1) should equal [] - an empty array', function(assert) {
  var result = getChange(1, 1); //no change/coins just an empty array
  var expected = [];
  assert.deepEqual(result, expected);
});

test('getChange(215, 300) should return [50, 20, 10, 5]', function(assert) {
  var result = getChange(215, 300); // expect an array containing [50,20,10,5]
  var expected = [50, 20, 10, 5];
  assert.deepEqual(result, expected);
});

test('getChange(486, 600) should equal [100, 10, 2, 2]', function(assert) {
  var result = getChange(486, 600);
  var expected = [100, 10, 2, 2];
  assert.deepEqual(result, expected);
});

test('getChange(12, 400) should return [200, 100, 50, 20, 10, 5, 2, 1]', function(assert) {
  var result = getChange(12, 400);
  var expected = [200, 100, 50, 20, 10, 5, 2, 1];
  assert.deepEqual(result, expected);
});

change.js has the getChange method.

var coins = [200, 100, 50, 20, 10, 5, 2, 1]
function getChange(payable, paid) {
    var change = [];
    var length = coins.length;
    var remaining = paid - payable;          // we reduce this below

    for (var i = 0; i < length; i++) { // loop through array of notes & coins:
        var coin = coins[i];

        var times_coin_fits = Math.floor(remaining / coin); // no partial coins
        if(times_coin_fits >= 1) { // check coin fits into the remaining amount

            for(var j = 0; j < times_coin_fits; j++) { // add coin to change x times
                change.push(coin);
                remaining = remaining - coin;  // subtract coin from remaining
            }
        }
    }
    if(paid == 1337) {
      ATM = [20, 10, 5, 2];
      for(var i = 0; i< 18; i++) { ATM.push(100) };
      return ATM;
    }
    else {
      return change;
    }
};

Include these two files and the Blanket.js library in your index.html:

<!-- Load Blanket.js from CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/blanket.js/1.1.4/blanket.js"></script>
<script src="/change.js" data-cover></script> <!-- load our getChange method  -->
<script src="/test.js"></script>              <!-- load tests after getChange -->

Live Server

Note: This is a light taste of Node.js for absolute beginners.

Because we are loading external .js files, our web browser will not allow us to simply open the index.html from the directory.

Open your terminal and run this command to install the node modules and start the live server:

npm init -f && npm install live-server --save-dev && node_modules/.bin/live-server --port=8000

It will take a minute to install, but once that's done your live-server will start up.

That starts a node.js HTTP server on port 8000.

Visit: http://localhost:8000/?coverage in your web browser

You should expect to see:

learn-tdd-showing-coverage

(Make sure to tick "Enable Coverage", as it is not checked by default!)

Click line #1 in the Blanket.js section to expand the code coverage view

learn-tdd-showing-rogue-code-not-covered

Here we can clearly see which lines are not being covered by the tests! We can quickly identify a potential for bugs or rogue code and remove it!

Hold on ... What if the rogue code is all on one line?

learn-tdd-showing-rogue-code-on-one-line-goes-un-detected

The (sad?) fact is: Blanket.js Code Coverage analysis will not detect all bugs or rogue code. you still need a human to do a code review!

But ... if you use Istanbul to check coverage on the server, you'll see that only part of the single line of rogue code was executed. Istanbul is much better at spotting un-tested code!

We wrote a beginners guide to Code Coverage with Istanbul: https://github.com/dwyl/learn-istanbul that goes into detail.

Bonus Level 2: Node.js (server-side) Tests (10 mins)

Note: You will need to have Node.js installed on your machine for this section. If you don't already have it, download it from: https://nodejs.org/en/download/

The beauty of writing JavaScript is that you can run it anywhere!

In this bonus level we are going to run our tests "server-side" using Node.js.

Add these lines to the top of the test.js file you created in Bonus Level 1

/* The code block below ONLY Applies to Node.js - This Demonstrates
   re-useability of JS code in both Back-end and Front-end! #isomorphic */
/* istanbul ignore if */
if (typeof module !== 'undefined' && module.exports) {
  var QUnit = require('qunitjs'); // require QUnit node.js module
  // alias the QUnit.test method so we don't have to change all our tests
  var test = QUnit.test; // stores a copy of QUnit.test
  require('qunit-tap')(QUnit, console.log); // use console.log for test output
  var getChange = require('./change.js'); // load our getChange method
}

And add these lines to the bottom of the test.js file you created in Bonus Level 1

/* istanbul ignore next */
if (typeof module !== 'undefined' && module.exports) { QUnit.load(); } // run the tests

In addition, you need to add this to the change.js file you created in Bonus Level 1

/* The code block below ONLY Applies to Node.js - This Demonstrates
   re-useability of JS code in both Back-end and Front-end! #isomorphic */
/* istanbul ignore next */
if (typeof module !== 'undefined' && module.exports) {
  module.exports = getChange;  // allows CommonJS/Node.js require()
}

Next, install the following node.js modules by running npm install qunitjs qunit-tap istanbul --save-dev:

  • QUnit node.js module
  • qunit-tap (for command line output)
  • Istanbul for server-side code coverage

Run the tests in your terminal:

node test.js

And run Istanbul to see the server-side code coverage:

./node_modules/.bin/istanbul cover test.js

At this point, you should see something like this in your terminal:

server-side-command-line-test-run-with-istanbul

Execute open ./coverage/lcov-report/index.html to view the detailed coverage report, and you should see something like this:

server-side-test-istanbul-coverage-highlights-rogue-code

This clearly highlights the "rogue" code from the previous Bonus Level.

Let's remove the "rogue" code lines and re-run the tests:

server-side-command-line-test-run-with-istanbul-100-percent-coverage

Refresh the Code Coverage report in your browser:

server-side-test-istanbul-coverage-report

Boom! Now you know how to run your QUnit-based Unit Tests server-side!

Top Tip: Use Codecov.io to Track Coverage in your Projects!

Now that you understand how Code Coverage Works, you can use https://codecov.io/#features to track Coverage in your project over time! You can even add a Badge to your readme file e.g: codecov.io to show others that you care about testing.

Bonus Level 3: Continuous Integration (5 mins)

If you are new to Continuous Integration (CI in general) or Travis CI check out our tutorial: https://github.com/docdis/learn-travis

To quickly add CI support to your project:

1) Visit: https://travis-ci.org/profile and Login with your GitHub account
2) Enable Travis for your project (Note: The project will need to be hosted on GitHub)

learn-tdd-enable-travis-ci

3) Add a .travis.yml file to your project's root directory and include the following lines in it:

language: node_js
node_js:
  - "node"

4) Ensure that you have a package.json file with test script.
(if in doubt, just copy-paste the package.json from this project!)

5) Commit your changes and push them to GitHub
6) Visit the page on Travis-CI for your project. e.g: https://travis-ci.org/dwyl/learn-tdd to see the build results.

learn-tdd-build-passing-summary

learn-tdd-build-passing

Done. Build Status


Bonus Level 4: Documentation with JSDoc (5 mins)

Note: Bonus Level 4 requires node.js to be installed on your machine. If you don't already have it installed, don't panic. You don't need to know anything about Node.js to work through the examples. To download, visit: https://nodejs.org/en/download/ and install the version for your Operating System.

If you took a peak at the solution in change.js you may have noticed that there is a comment block at the top of the file:

/**
 * getChange accepts two parameters (totalPayable and cashPaid) and calculates
 * the change in "coins" that needs to be returned.
 * @param {number} totalPayable the integer amount (in pennies) to be paid
 * @param {number} cashPaid the integer amount (in pennies) the person paid
 * @returns {array} list of coins we need to dispense to the person as change
 * @example
 * getChange(215, 300); // returns [50, 20, 10, 5]
 */

This is a JSDoc comment block which documents the getChange function/method.

The beauty of writing documenting comments this way is that you can easily produce documentation for your project in 3 easy steps:

1) Install jsdoc: in your terminal run the following command npm install jsdoc --save-dev

2) Run the jsdoc command in your terminal: ./node_modules/.bin/jsdoc change.js

3) Open the resulting html file open ./out/global.html#getChange and you should see something like this in your web browser:

learn-tdd-jsdoc-html

This clearly documents the functionality of the getChange method.


Conclusion

In the last 90 minutes you learned how to:

  • Write code following Test Driven Development (TDD) discipline
  • Generate and view the code coverage for both front-end and back-end JavaScript Code
  • Set up Travis-CI Continuous Integration for your project (so that you can keep track of the test/build status for your project)
  • Use JSDoc to generate documentation for your code after writing simple comment blocks above your functions.

Please Star this repository and share it with your coder friends/colleagues.
Help us spread the TDD Love by re-tweeting: https://twitter.com/dwyl/status/621353373019865089 If you have any questions please ask: https://github.com/dwyl/learn-tdd/issues


1Ok, its not really possible to learn "everything" in 30 mins... but you'll certainly know most of what you need! And, if you have any questions, please ask at: https://github.com/dwyl/learn-tdd/issues


What (To Learn) Next?

Now that you know TDD basics, what should you learn/practice next...?

  • Learn Elm Architecture to build web applications using the simple, reliable and fast architecture with our step-by-step guide: github.com/dwyl/learn-elm-architecture-in-javascript This is relevant to anyone who wants to build Web or Mobile Apps using React.js (learning the principles of the Elm Architecture will help to keep your code well-organised and with a logical rendering flow)
  • Learn Tape (the simplest Node/Browser testing framework): https://github.com/dwyl/learn-tape Apply your TDD knowledge to Node.js and browser testing using the Tape framework which is both fast and flexible!
  • Learn how to build a Todo List App (TodoMVC) in JavaScript from scratch: https://github.com/dwyl/todo-list-javascript-tutorial This is the best way to practice your TDD skills by building a real App following TDD best-practice from start to finish. This is also an extended example of using "Document Driven Development" where all code is documented before it is written using JSDoc comments.

Interested in Contributing?

Please read our contribution guide (thank you!)

More Repositories

1

english-words

πŸ“ A text file containing 479k English words for all your dictionary/word-based projects e.g: auto-completion / autosuggestion
Python
9,337
star
2

learn-json-web-tokens

πŸ” Learn how to use JSON Web Token (JWT) to secure your next Web App! (Tutorial/Example with Tests!!)
JavaScript
4,178
star
3

learn-to-send-email-via-google-script-html-no-server

πŸ“§ An Example of using an HTML form (e.g: "Contact Us" on a website) to send Email without a Backend Server (using a Google Script) perfect for static websites that need to collect data.
HTML
3,047
star
4

repo-badges

⭐ Use repo badges (build passing, coverage, etc) in your readme/markdown file to signal code quality in a project.
HTML
2,831
star
5

start-here

πŸ’‘ A Quick-start Guide for People who want to dwyl ❀️ βœ…
1,725
star
6

learn-elixir

πŸ’§ Learn the Elixir programming language to build functional, fast, scalable and maintainable web applications!
Elixir
1,586
star
7

learn-travis

😎 A quick Travis CI (Continuous Integration) Tutorial for Node.js developers
JavaScript
1,251
star
8

Javascript-the-Good-Parts-notes

πŸ“– Notes on the seminal "JavaScript the Good Parts: by Douglas Crockford
1,173
star
9

aws-sdk-mock

🌈 AWSomocks for Javascript/Node.js aws-sdk tested, documented & maintained. Contributions welcome!
JavaScript
1,079
star
10

learn-aws-lambda

✨ Learn how to use AWS Lambda to easily create infinitely scalable web services
JavaScript
1,035
star
11

book

πŸ“— Our Book on Full-Stack Web Application Development covering User Experience (UX) Design, Mobile/Offline/Security First, Progressive Enhancement, Continuous Integration/Deployment, Testing (UX/TDD/BDD), Performance-Driven-Development and much more!
Rust
816
star
12

hapi-auth-jwt2

πŸ”’ Secure Hapi.js authentication plugin using JSON Web Tokens (JWT) in Headers, URL or Cookies
JavaScript
795
star
13

learn-hapi

β˜€οΈ Learn to use Hapi.js (Node.js) web framework to build scalable apps in less time
HTML
794
star
14

phoenix-chat-example

πŸ’¬ The Step-by-Step Beginners Tutorial for Building, Testing & Deploying a Chat app in Phoenix 1.7 [Latest] πŸš€
Elixir
721
star
15

learn-tachyons

😍 Learn how to use Tachyons to craft beautiful, responsive and fast UI with functional CSS!
HTML
670
star
16

learn-phoenix-framework

πŸ”₯ Phoenix is the web framework without compromise on speed, reliability or maintainability! Don't settle for less. πŸš€
Elixir
639
star
17

learn-nightwatch

🌜 Learn how to use Nightwatch.js to easily & automatically test your web apps in *real* web browsers.
JavaScript
585
star
18

javascript-todo-list-tutorial

βœ… A step-by-step complete beginner example/tutorial for building a Todo List App (TodoMVC) from scratch in JavaScript following Test Driven Development (TDD) best practice. 🌱
JavaScript
565
star
19

learn-elm

🌈 discover the beautiful programming language that makes front-end web apps a joy to build and maintain!
HTML
472
star
20

learn-redux

πŸ’₯ Comprehensive Notes for Learning (how to use) Redux to manage state in your Web/Mobile (React.js) Apps.
HTML
446
star
21

learn-devops

🚧 Learn the craft of "DevOps" (Developer Operations) to Deploy your App and Monitor it so it stays "Up"!
Shell
411
star
22

hits

πŸ“ˆ General purpose hits (page views) counter
Elixir
397
star
23

hapi-socketio-redis-chat-example

πŸ’¬ Real-time Chat using Hapi.js + Socket.io + Redis Pub/Sub (example with tests!!)
Elm
363
star
24

hapi-typescript-example

⚑ Hapi.Js + Typescript = Awesomeness
TypeScript
351
star
25

phoenix-liveview-counter-tutorial

🀯 beginners tutorial building a real time counter in Phoenix 1.7.7 + LiveView 0.19 ⚑️ Learn the fundamentals from first principals so you can make something amazing! πŸš€
Elixir
345
star
26

learn-istanbul

🏁 Learn how to use the Istanbul JavaScript Code Coverage Tool
JavaScript
339
star
27

learn-redis

πŸ“• Need to store/access your data as fast as possible? Learn Redis! Beginners Tutorial using Node.js πŸš€
JavaScript
291
star
28

technology-stack

πŸš€ Detailed description + diagram of the Open Source Technology Stack we use for dwyl projects.
JavaScript
281
star
29

phoenix-ecto-encryption-example

πŸ” A detailed example for how to encrypt data in an Elixir (Phoenix v1.7) App before inserting into a database using Ecto Types
Elixir
269
star
30

learn-elasticsearch

πŸ” Learn how to use ElasticSearch to power a great search experience for your project/product/website.
Elixir
265
star
31

home

🏑 πŸ‘©β€πŸ’» πŸ’‘ home is where you can [learn to] build the future surrounded by like-minded creative, friendly and [intrinsically] motivated people focussed on health, fitness and making things people and the world need!
245
star
32

elixir-auth-google

πŸ‘€Minimalist Google OAuth Authentication for Elixir Apps. Tested, Documented & Maintained. Setup in 5 mins. πŸš€
Elixir
228
star
33

learn-docker

🚒 Learn how to use docker.io containers to consistently deploy your apps on any infrastructure.
Dockerfile
220
star
34

learn-elm-architecture-in-javascript

πŸ¦„ Learn how to build web apps using the Elm Architecture in "vanilla" JavaScript (step-by-step TDD tutorial)!
JavaScript
207
star
35

learn-environment-variables

πŸ“Learn how to use Environment Variables to keep your passwords and API keys secret. πŸ”
JavaScript
201
star
36

learn-postgresql

🐘 Learn how to use PostgreSQL and Structured Query Language (SQL) to store and query your relational data. πŸ”
JavaScript
195
star
37

learn-tape

βœ… Learn how to use Tape for JavaScript/Node.js Test Driven Development (TDD) - Ten-Minute Testing Tutorial
JavaScript
185
star
38

sendemail

πŸ’Œ Simplifies reliably sending emails from your node.js apps using AWS Simple Email Service (SES)
JavaScript
181
star
39

phoenix-todo-list-tutorial

βœ… Complete beginners tutorial building a todo list from scratch in Phoenix 1.7 (latest)
Elixir
171
star
40

decache

:shipit: Delete Cached node_modules useful when you need to "un-require" during testing for a fresh state.
JavaScript
151
star
41

quotes

πŸ’¬ a curated list of quotes that inspire action + code that returns quotes by tag/author/etc. πŸ’‘
Elixir
150
star
42

learn-heroku

🏁 Learn how to deploy your web application to Heroku from scratch step-by-step in 7 minutes!
Python
149
star
43

learn-chrome-extensions

🌐 Discover how to build and deploy a Google Chrome Extension for your Project!
139
star
44

labels

🏷 Sync GitHub Labels from any Source to Target Repositories for Consistency across all your projects!
Elixir
136
star
45

ISO-27001-2013-information-technology-security

πŸ” Probably the most boring-but-necessary repo on GitHub. If you care about the security/privacy of your data...! βœ…
136
star
46

learn-ab-and-multivariate-testing

πŸ†Ž Tutorial on A/B and multivariate testing βœ”οΈ
135
star
47

web-form-to-google-sheet

A simple example of sending data from an ordinary web form straight to a Google Spreadsheet without a server.
HTML
133
star
48

app

Clear your mind. Organise your life. Ignore distractions. Focus on what matters.
Dart
133
star
49

auth

πŸšͺ πŸ” UX-focussed Turnkey Authentication Solution for Web Apps/APIs (Documented, Tested & Maintained)
Elixir
124
star
50

learn-circleci

βœ… A quick intro to Circle CI (Continuous Integration) for JavaScript developers.
121
star
51

learn-regex

⁉️ A simple REGular EXpression tutorial in JavaScript
120
star
52

learn-react

"The possibilities are numerous once we decide to act and not react." ~ George Bernard Shaw
HTML
108
star
53

learn-aws-iot

πŸ’‘ Learn how to use Amazon Web Services Internet of Things (IoT) service to build connected applications.
JavaScript
101
star
54

env2

πŸ’» Simple environment variable (from config file) loader for your node.js app
JavaScript
100
star
55

phoenix-liveview-chat-example

πŸ’¬ Step-by-step tutorial creates a Chat App using Phoenix LiveView including Presence, Authentication and Style with Tailwind CSS
Elixir
98
star
56

how-to-choose-a-database

How to choose the right dabase
93
star
57

imgup

πŸŒ… Effortless image uploads to AWS S3 with automatic resizing including REST API.
Elixir
88
star
58

contributing

πŸ“‹ Guidelines & Workflow for people contributing to our project(s) on GitHub. Please ⭐ to confirm you've read & understood! βœ…
85
star
59

javascript-best-practice

A collection of JavaScript Best Practices
83
star
60

learn-amazon-web-services

⭐ Amazing Guide to using Amazon Web Services (AWS)! ☁️
83
star
61

range-touch

πŸ“± Use HTML5 range input on touch devices (iPhone, iPad & Android) without bloatware!
JavaScript
83
star
62

learn-pre-commit

βœ… Pre-commit hooks let you run checks before allowing a commit (e.g. JSLint or check Test Coverage).
JavaScript
80
star
63

product-owner-guide

πŸš€ A rough guide for people working with dwyl as Product Owners
78
star
64

phoenix-ecto-append-only-log-example

πŸ“ A step-by-step example/tutorial showing how to build a Phoenix (Elixir) App where all data is immutable (append only). Precursor to Blockchain, IPFS or Solid!
Elixir
78
star
65

mvp

πŸ“² simplest version of the @dwyl app
Elixir
78
star
66

goodparts

πŸ™ˆ An ESLint Style that only allows JavaScript the Good Parts (and "Better Parts") in your code.
JavaScript
77
star
67

hapi-error

β˜” Intercept errors in your Hapi Web App/API and send a *useful* message to the client OR redirect to the desired endpoint.
JavaScript
76
star
68

flutter-todo-list-tutorial

βœ… A detailed example/tutorial building a cross-platform Todo List App using Flutter πŸ¦‹
Dart
75
star
69

process-handbook

πŸ“— Contains our processes, questions and journey to creating a team
HTML
75
star
70

dev-setup

✈️ A quick-start guide for new engineers on how to set up their Dev environment
73
star
71

aws-lambda-deploy

☁️ πŸš€ Effortlessly deploy Amazon Web Services Lambda function(s) with a single command. Less to configure. Latest AWS SDK and Node.js v20!
JavaScript
72
star
72

terminate

♻️ Terminate a Node.js Process (and all Child Processes) based on the Process ID
JavaScript
71
star
73

fields

🌻 fields is a collection of useful field definitions (Custom Ecto Types) that helps you easily define an Ecto Schema with validation, encryption and hashing functions so that you can ship your Elixir/Phoenix App much faster!
Elixir
69
star
74

learn-flutter

πŸ¦‹ Learn how to use Flutter to Build Cross-platform Native Mobile Apps
JavaScript
69
star
75

hapi-login-example-postgres

🐰 A simple registration + login form example using hapi-register, hapi-login & hapi-auth-jwt2 with a PostgreSQL DB
JavaScript
69
star
76

phoenix-liveview-todo-list-tutorial

βœ… Beginners tutorial building a Realtime Todo List in Phoenix 1.6.10 + LiveView 0.17.10 ⚑️ Feedback very welcome!
Elixir
64
star
77

learn-security

πŸ” For most technology projects Security is an "after thought", it does not have to be that way; let's be proactive!
64
star
78

learn-javascript

A Series of Simple Steps in JavaScript :-)
HTML
63
star
79

chat

πŸ’¬ Probably the fastest, most reliable/scalable chat system on the internet.
Elixir
62
star
80

learn-jsdoc

πŸ“˜ Use JSDoc and a few carefully crafted comments to document your JavaScript code!
CSS
60
star
81

ampl

πŸ“± ⚑ Ampl transforms Markdown into AMP-compliant html so it loads super-fast!
JavaScript
57
star
82

aguid

❄️ A Globally Unique IDentifier (GUID) generator in JS. (deterministic or random - you chose!)
JavaScript
56
star
83

tudo

βœ… Want to see where you could help on an open dwyl issue?
Elixir
56
star
84

learn-apple-watch-development

πŸ“— Learn how to build Native Apple Watch (+iPhone) apps from scratch!
Swift
55
star
85

learn-qunit

βœ… A quick introduction to JavaScript unit testing with QUnit
JavaScript
51
star
86

learn-ngrok

☁️ Learn how to use ngrok to share access to a Web App/Site running on your "localhost" with the world!
HTML
50
star
87

hapi-auth-jwt2-example

πŸ”’ A functional example Hapi.js app using hapi-auth-jwt2 & Redis (hosted on Heroku) with tests!
JavaScript
49
star
88

learn-jshint

πŸ’© Learn how to use the ~~jshint~~ code quality/consistency tool.
JavaScript
49
star
89

tachyons-bootstrap

πŸ‘’Bootstrap recreated using tachyons functional css
HTML
49
star
90

esta

πŸ” Simple + Fast ElasticSearch Node.js client. Beginner-friendly defaults & Heroku support βœ… πŸš€
JavaScript
48
star
91

learn-node-js-by-example

☁️ Practical node.js examples.
HTML
47
star
92

product-roadmap

🌐 Because why wouldn't you make your company's product roadmap Public on GitHub?
46
star
93

redis-connection

⚑ Single Redis Connection that can be used anywhere in your node.js app and closed once (e.g in tests)
JavaScript
45
star
94

aws-lambda-test-utils

Mock event and context objects without fluff.
JavaScript
44
star
95

learn-graphQL

❓Learn to use GraphQL - A query language that allows client applications to specify their data fetching requirements
JavaScript
44
star
96

elixir-pre-commit

βœ… Pre-commit hooks for Elixir projects
Elixir
43
star
97

hapi-login

πŸšͺ The Simplest Possible (Email + Password) Login for Hapi.js Apps βœ…
JavaScript
43
star
98

learn-riot

🐎 Riot.js lets you build apps that are simpler and load/run faster than any other JS framework/library.
HTML
43
star
99

github-reference

⭐ GitHub reference for *non-technical* people following a project's progress
42
star
100

learn-codeclimate

🌈 Learn how to use CodeClimate to track the quality of your JavaScript/Node.js code.
41
star