knowledge-kitchen

Unit Testing - Validition of Smallest Testable Unit of Code

Validating the behavior of functions.

  1. Overview
  2. Example
  3. Advantages
  4. Test Cases
  5. Assertions
  6. Best Practices
  7. Doubles
  8. Testing Tools
  9. Conclusions

Overview

Concept

A unit is the smallest testable unit of code - typically a function, but sometimes an object or class.

Background

Unit testing is an automated testing technique developed in 1992 by Kent Beck to solve the annoyance of manually testing code for obvious errors.

Other languages

The world of unit testing frameworks for different languages that are based on the original JUnit is called xUnit.

Advantages

Comparison to Alternative

Automated unit testing has many advantages over manual debugging:

Relationship to Refactoring

Refactoring of existing code involves rewriting it to make it better organized and efficient without changing its observable behavior.

In almost all cases, I’m opposed to setting aside time for refactoring.”

-Martin Fowler, author of ‘Refactoring’, the book

Examples

Java

A unit test example in Java using JUnit:

// include JUnit and assertion libraray
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class MyFirstTest {

    @Test
    public void multiplicationOfZeroIntegersShouldReturnZero() {
        MyClass tester = new MyClass(); // MyClass is tested

        // assert statements for a variety of test cases
        assertEquals(0, tester.multiply(10, 0), "10 x 0 must be 0");
        assertEquals(0, tester.multiply(0, 10), "0 x 10 must be 0");
        assertEquals(0, tester.multiply(0, 0), "0 x 0 must be 0");
    }
}

Javascript

A unit test example in Javascript using mocha:

// use mocha's built-in assertion library
const assert = require("assert")

// a set of tests of array functions
describe("Array", function () {
  // one particular unit test
  describe("#indexOf()", function () {
    // assert what should be returned
    it("should return -1 when the value is not present", function () {
      // test that assertion
      assert.equal(-1, [1, 2, 3].indexOf(4))
    })
  })
})

If the project’s package.json file is set up with a script called test that contains the command to execute the tests using, then the tests can be run from the same directory as the package.json file with npm test.

Javascript (continued)

A unit test example of a back-end route using mocha, chai, and the chai-http extension for simple testing of routes.

describe("GET request to /foo route", () => {
  it("it should respond with an HTTP 200 status code and an object in the response body", done => {
    chai
      .request(server)
      .get("/foo")
      .end((err, res) => {
        res.should.have.status(200) // use should to make BDD-style assertions
        res.body.should.be.a("object") // our route sends back an object
        res.body.should.have.property("success", true) // a way to check the exact value of a property of the response object
        done() // resolve the Promise that these tests create so mocha can move on
      })
  })
})

Python

A unit test example in Python using pytest:

import pytest
from some_package import some_module

class Tests:

  def test_some_function(self):
    '''
    Verify some_function() returns a non-empty string.
    '''
    actual = some_module.some_function() # get the actual return value of the function
    assert isinstance(actual, str), f"Expected some_function() to return a string. Instead, it returned {actual}"
    assert len(actual) > 0, f"Expected some_function() not to be empty. Instead, it returned a string with {len(actual)} characters"

Note that pytest works best when all test files are in a tests directory with filenames that start with test_ and all test functions start with test_.

Python (continued)

A unit test example of a back-end route using the pytest-flask extension for simple testing of routes.

import pytest
from project import create_app

class Tests:

  def test_foo(self):
      app = create_app() # this method must be defined in production code and must return the flask app object
      with app.test_client() as test_client:
          response = test_client.get('/foo')
          response_data = response.data.decode('utf8') # get plain text response data
          assert response.status_code == 200 "/foo should respond with an HTTP 200 status code"
          # if the response data is expected to be in JSON format
          response_obj = json.loads(response_data) # convert json response body data into an object
          assert response_obj.success == True, "Expected the response JSON to have a property 'success' with value True"
          # ... or if the response data is expected to be HTML format
          assert "<p>foobar</p>" in response_data, "Expected the response HTML to have a paragraph with 'foobar' in it"

Test Cases

Minimal Number

Unit tests must test at least two scenarios, called test cases:

In actuality, there will usually be many test cases per production function for every possible variety of valid and invalid input.

Assertions

Concept

Every unit testing framework depends upon a set of assertions - purported truths that are then compared against reality.

Typical Assertions

This is a partial list of methods in JUnit’s asssertion library that all other testing frameworks tend to imitate:

Equality:

Truth:

Existence:

Sameness:

Setup & Teardown

Overview

xUnit unit testing frameworks all support the concept of setup and teardown functions.

Example - Java junit

import org.junit.jupiter.api.*;

class JUnit5ExampleTest {

    @BeforeAll
    static void beforeAll() {
        System.out.println("Before all tests");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("Before each individual test");
    }

    @AfterEach
    void afterEach() {
        System.out.println("After each individual test");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("After all tests");
    }

} // the end

Best Practices

TRIPinesss

According to “Pragmatic Unit Testing in Java with JUnit”, by Andy Hunt and Dave Thoma, good unit tests are TRIP-y:

Three pillars

According to Roy Osherove, author of “The Art of Unit Testing”, there are three main pillars of a good unit test:

Listen to a song about unit testing by Roy Osherove

Doubles

Concept

Unit tests must run in isolation of one another and of external dependencies, such as databases, APIs, or other servers.

Types of double

There are three commonly-used types of doubles.

Mocks

Mocks are replacements for external interfaces.

Stubs

Whereas mocks are called by production code and passed arguments in place of a real external dependency, stubs are used to return pre-defined responses to the calling code.

Fakes

Like, mocks and stubs, fakes are used in the simulation of an external dependency. But whereas mocks and stubs are simple code substitutions for the real thing, a fake is an actually functioning replacement for the external dependency that runs on the local machine.

Testing Tools

Javascript

The following are the recommended tools for unit testing in Node.js Javascript.

Javascript (continued)

Javascript (continued again)

To mock MongoDB databases connected to Javascript projects, try the following:

Python

The following are the recommended tools for unit testing in Python.

Documentation on each of these sites is reasonably good.

Python (continued)

To mock MongoDB databases connected to Python projects, try the following:

Python (continued again)

To learn more about unit testing in Python, watch the LinkedIn Learning video “Unit Testing and Test Driven Development

Conclusions

These notes have attempted to give you a high-level overview of the intentions and directions of unit testing. Now go and try it.