No description, website, or topics provided.
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
example01
lab01
.gitignore
README.md
red-green-refactor.png
vegetables.jpg

README.md

Testing and TDD

vegetables

Objectives

After this lesson students should be able to:

  • Identify main advantages to writing automated tests and TDD
  • Comfortably explore test framework documentation
  • Write and execute small unit tests in jest

In short what are automated tests?

  • Automated tests are small bits of code that verify that application code is correct
  • Similar to double entry accounting
  • Ensure a minimum threshold of expected behavior
  • Can remove some of the burdens of manual testing
  • Automated tests are the foundation for a programming methodology called "Test Drive Development" where tests are written before any production code to guide and organize the development process

An image of testing in practice

Why do we bother writing tests?

For the sake of yourself and your team

Tests are used during the development process to:

  • Ensure our code works as we write it
    • Red, Green, Refactor
  • Ensure our code works after we write it
    • Changes and new features will cause specs to fail. This is a good thing! Either the spec needs to be updated or the change does break something!
  • Prove to your team (and anyone using your code) that it works
    • When working on teams, your coworkers will look for specs in your PR. If they see good specs, they know your code works without having to read every line of code.
  • Make debugging faster later on
    • Look at the specs to see what your coverage is. If you have proof one part of the chain should work, check the next, and next. Eventually you may find a part of the chain that is not covered. Write a failing spec and then fix the bug!

For the sake of others

  • Good, organized tests can be used as a form of documentation
    • Don't know how to use a function? Read the specs!
    • Don't know how a feature works? Read the specs!
    • Don't know how to make an API request? What the response should be? Read the specs!
  • There is a very strong (inverse) correlation between number of bugs and number of specs

For the sake of getting a job

As always, you want to have these skills to get a job

  • Put some testing libraries on your resume or even just TDD
  • Some interviewers ask you to write specs or make specs pass during the interview
  • You will get HUGE bonus points if you submit a coding challenge with your own specs

I do (unit test a simple function)

Create a new npm package and include jest

  • First cd into a new directory, then:
npm init
...<just hit enter at each prompt>
npm install --save-dev jest
npm install

Write a small function

function fibs(n) {
  // a function for calculating the Nth fibonacci number

  // declare vars
  let last = 1;
  let next = 0;
  let i = 1;

  while (i < n) {
    const tmp = last;
    last = next + last;
    next = tmp;
    i += 1;
  }

  return last;
}

module.exports = fibs;

Normally we'd just tack on a console log to test. . .

console.log('third fib: ', fibs(3));

But this approach does not scale

Let's add automated tests for this function

First, add the following test script to package.json

{
  "name": "example01",         
  "version": "1.0.0",          
  "description": "",
  "main": "index.js",
  "scripts": {                 
    "test": "jest"
  },
  "author": "",
  "license": "ISC"
}

Then begin writing a unit test for the fibs function

const fibs = require('./fibs');

test('it computes the first fib', () => {
  expect(fibs(1)).toBe(1);
});

And run the test like so

npm test

Now add a few more tests

const fibs = require('./fibs');

test('it computes the first fib', () => {
  expect(fibs(1)).toBe(1);
});

test('it computes the second fib', () => {
  expect(fibs(2)).toBe(1);
});

test('it computes the third fib', () => {
  expect(fibs(3)).toBe(2);
});

test('it computes the fourth fib', () => {
  expect(fibs(4)).toBe(3);
});

test('it computes the fifth fib', () => {
  expect(fibs(5)).toBe(5);
});

You Do: Lab

  • navigate to the lab directory in this repo and complete the tasks outlined in the README

There are many flexible helper methods for testing different types of data:

You Do:

First, review the Matchers section above How would you write a test to check if the number 4 is in an array?

test('has 4 as an element', () => {
    arr = [1, 2, 3, 4];
    expect(arr) //. . . ?
  });

But we can also handle odd edge cases

  • What will our function do with 0 as an input?
test('it handles 0', () => {
  expect(fibs(0)).toBe(0);
});

// => fails

Let's adjust our function so handle this new failing test by adding an explicit check for 0 at the top

function fibs(n) {
  // a function for calculating the Nth fibonacci number

  if (n < 1) {
    return 0;
  }

  // . . .
}

And it passes!

What about null or other invalid values?

test('it handles null', () => {
  expect(fibs(null)).toBe(null);
});

test('it handles a string', () => {
  expect(fibs("chars")).toBe(null);
});
  • Watch these fail, then get the tests to pass by fixing our function again
function fibs(n) {
  // a function for calculating the Nth fibonacci number

  // handle edge cases
  if (typeof n !== 'number') {
    return null;
  }

  if (n < 1) {
    return 0;
  }

  . . .
}

Last (but not least) build a new implementation keeping the existing tests

function fibs(n) {
  if (n < 2) {
    return 1;
  }

  return fibs(n - 1) + fibs(n - 2);
}
  • Run the tests and see them "turn red"

Select one test at a time using only

test.only('it computes the first fib', () => {
  expect(fibs(1)).toBe(1);
});

Find the offending test then fix it with an adjustment

function fibs(n) {
  if (n < 3) {
    return 1;
  }

  return fibs(n - 1) + fibs(n - 2);
}

Run the tests again, see the edge case tests still red then fix the new function

function fibs(n) {
  // handle edge cases
  if (typeof n !== 'number') {
    return null;
  }

  if (n < 1) {
    return 0;
  }

  // base case
  if (n < 3) {
    return 1;
  }

  return fibs(n - 1) + fibs(n - 2);
}

Red, Green, Refactor

red-green-refactor

When writing new specs we always go from Red to Green then finally Refactor.

Red

We start by writing specs that we want to pass. Since we are writing the specs before the feature, the specs will fail. If most or all of our specs do not fail, something is probably wrong. We want these to fail so we know that our specs can fail when they don't work.

Green

Then we write the feature. If it works our specs should all turn green. If they don't, something is probably wrong with the feature. Write the smallest amount of code to make the tests go green. Your code does not have to be perfect here. When everything is green you can be confident it works because you know you have specs that can fail!

Commit your code here!

Sometimes the issue is with the spec itself. When that is the case, fix the spec. But you still must make sure that spec fails when you remove the feature! Your instinct, however, should be that something is wrong with the feature, not the spec.

Refactor

Now you may refactor your code if necessary. If everything passes after you refactor, you can be confident your refactor did not break the code! If a test goes red, your refractor probably broke something. Fix the refactor, not the test!

Commit (or amend) your code here!

Describe

Sections have tests can be organized together in a describe block.

This allows us to logically group sections of tests together

describe('matching cities to foods', () => {
  // Applies only to tests in this describe block
  beforeEach(() => {
    return initializeFoodDatabase();
  });

  test('Vienna <3 sausage', () => {
    expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
  });

  test('San Juan <3 plantains', () => {
    expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
  });
});

Further Resources