Skip to content

50.003 - Software Testing

Learning Outcomes

By the end of this unit, you should be able to

  1. Explain what is software testing
  2. Articulate the purpose of software testing
  3. List the components of a test
  4. List the components of a test case
  5. Develop simple testsuite for JavaScript programs using Jest

Software testing

Software testing is one of the methods to check and validate the produced software is behaving according to the specification.

Furthermore software testing helps to identify potential flaws in

  • architectural design
  • software security
  • software performance

The following is a quote adopted from IBM https://www.ibm.com/topics/software-testing.

"Software testing is the process of evaluating and verifying that a software product or application does what it is supposed to do. The benefits of testing include preventing bugs, reducing development costs and improving performance."

Why software testing

The answer this question. One way is to find out what if we do not conduct software testing? In particular for large scale mission critical systems, the absence of testing often leads to epic failures and excessive monetary cost.

For instance in 1998, NASA launched a Mars Climate Orbiter. However due to an English Unit to Metric translation errors, the Orbiter lost contact after it passed behind Mars.

Anantomy of software testing

What is a test?

A test in a software system is an act to exercise software with test cases. A test have two goals

  1. to find faults.
  2. to show that the software behaves according to expectation (specification), i.e. to build confidence.

graph
  Input-->Program
  Program-->TO
  TO["Test Oracle"]-->Pass
  TO["Test Oracle"]-->Fail
A test is a process of feeding the generated input to the test subject, i.e. the program, the output will be verified by a test oracle, which returns either Pass or Fail as result. The test oracle is often implemented as another program.

What is a test case?

A test case is a formal documentation of a test. A test case consists of

  • an identifier (often linked to the user case identifier).
  • a description of the purpose and the use case description being tested.
  • a precondition
  • a set of inputs
  • the expected output
  • the expected postcondition
  • the execution history (the log)

Limitation of Software testing

Despite the importance of testing, testing alone is insufficient to ensure software free from bugs, since a failed test implies the existence of a software bug, a passed test implies that the software behaves according to the specification when given a particular instance of the input. But what about other input instances? What if the domain of the input is infinite.

"Testing shows the presence, not the absence of bugs" -- Edsger Dijkstra

Testing a Node.js app

There are many tools for testing Node.js app. Jest is the one of the most popular option.

To add Jest to a Node.js project, we can run

npm i jest
in the project description file package.json change the follow line

"test": "echo \"Error: no test specified\" && exit 1"
to
"test": "jest"

Consider in the src/mymath.js

//
function sum(x,y) {
    return x + y;
}

module.exports =  {sum}

In the test/mymath.test.js

const mymath = require('../src/mymath.js');

describe("mymath sum test-suite", () => {
    test ("summing of two positive numbers", () => {
        const result = mymath.sum(1,2);
        expect(result).toBe(3);
    });
    test ("summing of two negative numbers", () => {
        const result = mymath.sum(-3,-2);
        expect(result).toBe(-5);
    });
})

The describe() function defines a set of tests should be logically grouped together as a test suite. In the above case, in the test suite consists of two tests. Each test is define by a call to test() function, which expect a description fo the test, the action test function. In the test function, we compute the actual result by calling the test subject, sum(), and compares it with the execpted result.

To execute the test, we run

npm run test
which runs the above test suite against the test subject and generates the following report in the console.

> my_test_app@1.0.0 test
> jest

 PASS  test/mymath.test.js
  mymath sum tests
     summing of two positive numbers (12 ms)
     summing of two negative numbers (3 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.35 s, estimated 1 s
Ran all test suites.

Testing OOP

We can use Jest to test an object instance of a class, consider the following file src/FibSeq.js

// src/FibSeq.js
class FibSeq {
    constructor() {
        this.prev = 0;
        this.curr = 1;
    }
    next() {
        let res = this.prev + this.curr;
        this.prev = this.curr;
        this.curr = res;
        return res;
    }
}

module.exports = FibSeq;

We can test it with the following test suite

const FibSeq = require('../src/FibSeq.js');

describe("FibSeq class test", () => {
    const fibSeq = new FibSeq();
    test ("first fib num is 1", () => {
        const result = fibSeq.next();
        expect(result).toBe(1);
    });
    test ("second fib num is 2", () => {
        const result = fibSeq.next();
        expect(result).toBe(2);
    });
})
Which will run the two tests in sequence.

If we want to have the object being reset for each test case.

describe("FibSeq class test with setup and tear down", () =>{
    let fibSeq = null;
    beforeEach(() => {
        fibSeq = new FibSeq();
    });
    test ("first fib num is 1 after reset", () => {
        const result = fibSeq.next();
        expect(result).toBe(1);
    });
    test ("second fib num is 1 after reset", () => {
        const result = fibSeq.next();
        expect(result).toBe(1);
    });
    afterEach(() => {
        fibSeq = null;
    })
})

Further Readings

  • https://www.lambdatest.com/jest