munotes®

Unit Testing: What It Is For

Get access to whole semester resourcesSemester Pass

Chapter Thirty-Three

Syllabus topic Module 1, "Software Testing Strategies: Unit Testing: purpose"

Pages 183 to 187 of 622

In one line

Unit testing checks each smallest testable piece of a program on its own, as soon as it is written, so that a mistake is found where it was made, while it is cheap to fix and before other code is built on it.

In the wording a student can write in an examination: a unit is a "separately testable element specified in the design of a computer software component" (ISO/IEC/IEEE 24765); a unit test is the "testing of individual routines and modules by the developer or an independent tester" (ISO/IEC TR 7052:2023). The ISTQB syllabus calls the level component testing, which "focuses on testing components in isolation" and "is normally performed by developers in their development environments". Its purpose is to find defects in each unit's own logic early and in one known place; to check the unit's interface, local data, boundaries, paths and error handling; and to leave behind tests that can be rerun after every change.

What a unit is

The standards give three angles on the word. A unit is a "separately testable element specified in the design of a computer software component", a "logically separable part of a computer program", and a "software element that is not subdivided into other components or elements" (all ISO/IEC/IEEE 24765). ISO/IEC/IEEE 12207:2026 calls it a software unit: an "atomic-level software component of the software architecture that can be subjected to standalone testing".

The key phrase in every version is separately testable. In Python a unit is usually a function or a class; in Java a class or a method; in a web application a single request handler or service. ExamReg's module tree from earlier chapters gives natural units: the password check, the eligibility check, the paper selection, the fee calculator, the payment gateway adapter, the seat allocation and the PDF generator. Each can be called on its own, given inputs, and checked.

What unit testing is for

Unit testing earns its place in a strategy for four reasons.

  1. It finds defects where they are made. When a unit test fails, the fault is in that unit, a few dozen lines at most. When a system test fails, the fault could be anywhere, as Chapter Thirty-One, on the strategic approach, showed.
  2. It finds them early, when they are cheapest. Boehm and Basili's first finding was that "Finding and fixing a software problem after delivery is often 100 times more expensive than finding and fixing it during the requirements and design phase." They added a qualification that fits a project of ExamReg's size: for "small, noncritical software systems" the factor is "more like 5:1 than 100:1". Either way, the unit test is the earliest dynamic test there is.
  3. It lets the code be changed safely. A unit's tests stay with it, and rerunning them after every change is the cheapest regression testing a project has.
  4. It improves the design. A unit that is hard to test on its own is usually doing too much or depending on too much; writing its tests exposes that, which is why testability is a design property (Chapter Twenty-One, on how quality factors shape testing).
munotes.in183

Unit Testing: What It Is For

The standards stress one more thing: unit tests are normally written by the developer who wrote the unit. The ISTQB syllabus says component testing "is normally performed by developers in their development environments", and ISO/IEC TR 7052 allows "the developer or an independent tester". It is the one level where the author is expected to test their own work, because no one else knows the unit's inside as well, and the tests are needed long before a test team sees the code.

What a unit test checks

A useful checklist for testing one unit has five parts.

AspectThe questionFor ExamReg's fee calculator
InterfaceDoes the unit accept what its callers send, and return what they expect?Takes days late, backlog papers and a concession flag; returns a whole number of rupees
Local dataAre its own variables set, updated and used correctly?The running fee starts at the form fee plus backlog fees and is reduced only by the concession
BoundariesIs every edge right, at and just either side of it?0, 7, 8, 15 and 16 days late; zero backlog papers
PathsIs every branch of the unit taken at least once?With and without a concession; each late-fee band; the refusal
Error handlingIs bad input refused cleanly, with a clear message?Negative days, negative backlog papers, anything that is not a whole number

Each aspect has a chapter of its own later. Boundaries become boundary value analysis (Chapter Fifty-Five); paths become branch testing and, made exact, basis path testing (Chapter Seventy-Two); interfaces between units become integration testing (Chapter Thirty-Seven).

Worked example: unit testing the fee calculator

Here is ExamReg's fee calculator, with the fee rules fixed for this book: a form fee of Rs 800, Rs 150 for each backlog paper, the late-fee bands, forms more than 15 days late or with a negative number of days refused, and a concession that waives the form fee only. The checks below take the five aspects in turn. Each is a line of ordinary Python that calls the unit with chosen inputs and compares the answer with the rule; Chapter Thirty-Five, on writing unit tests with a framework, shows the same checks as a proper test suite.

FORM_FEE, BACKLOG_FEE = 800, 150

def total_fee(days_late, backlog_papers, concession):      # the unit under test
    if days_late < 0 or days_late > 15:
        raise ValueError("form not accepted")
    fee = FORM_FEE + BACKLOG_FEE * backlog_papers
    if concession:
        fee -= FORM_FEE                                      # a concession waives the form fee
    if days_late == 0:
        late = 0
    elif days_late <= 7:
        late = 100
    else:
        late = 500
    return fee + late

def refused(*args):
    try:
        total_fee(*args)
    except ValueError:
        return True
    return False

checks = {
    "interface: returns a whole number of rupees": isinstance(total_fee(0, 0, False), int),
    "local data: form fee plus two backlog papers": total_fee(0, 2, False) == 1100,
    "boundary: 7 days late pays Rs 100 late fee": total_fee(7, 0, False) == 900,
    "boundary: 8 days late pays Rs 500 late fee": total_fee(8, 0, False) == 1300,
    "boundary: 15 days late is accepted": total_fee(15, 0, False) == 1300,
    "boundary: 16 days late is refused": refused(16, 0, False),
    "path: no concession, on time": total_fee(0, 0, False) == 800,
    "path: concession, 3 days late, 2 backlog papers": total_fee(3, 2, True) == 400,
    "error handling: negative days refused": refused(-1, 0, False),
    "error handling: negative backlog papers refused": refused(0, -2, False),
}
for name, passed in checks.items():
    print("pass" if passed else "FAIL", " ", name)
print(f"{sum(checks.values())} of {len(checks)} checks passed;",
      "total_fee(0, -2, False) returned", total_fee(0, -2, False))
munotes.in184

Unit Testing: What It Is For

pass   interface: returns a whole number of rupees
pass   local data: form fee plus two backlog papers
pass   boundary: 7 days late pays Rs 100 late fee
pass   boundary: 8 days late pays Rs 500 late fee
pass   boundary: 15 days late is accepted
pass   boundary: 16 days late is refused
pass   path: no concession, on time
pass   path: concession, 3 days late, 2 backlog papers
pass   error handling: negative days refused
FAIL   error handling: negative backlog papers refused
9 of 10 checks passed; total_fee(0, -2, False) returned 500

Nine checks pass, and the one that fails is exactly the kind a unit test exists to catch. The calculator validates days late but never validates backlog papers, so a student record corrupted to hold -2 backlog papers produces a bill of Rs 500: the form fee of Rs 800 less Rs 300 for "negative" papers. No end-to-end system test is likely to try that input, because the form's own screen would never offer it; but a unit can be called by any other code, including a data import or a future screen, and it has to defend itself. The fix is one line at the top of the unit, refusing a negative or non-whole number of backlog papers as it already refuses bad days, and the failing check becomes the confirmation test for the fix.

Three things about the example are true of unit testing in general. The tests ran in a fraction of a second, so they can run every time the code is saved. The failure named the unit and the input exactly, so there was nothing to debug but one condition. And the checks stay with the code, so the next person to change total_fee inherits a record of what it must do.

munotes.in185

Unit Testing: What It Is For

What it does not mean

Unit testing is not debugging. The test shows that the unit fails for -2 backlog papers; finding and fixing the missing check is debugging, a separate activity (Chapter Forty-Seven).

Passing unit tests do not show that the units work together. The fee calculator can pass all its tests while the payment adapter misreads the total it returns; that is integration testing's job.

A unit is not always one function. It is the smallest separately testable element, which may be a class or a small module.

Unit tests are not a chore to add at the end. Written as the unit is written, or before it as the next chapters show, they cost least and find most.

Quick revision

  • Unit: a separately testable element of a software component; software unit: an atomic-level component that can be tested standalone.
  • Unit test: testing of individual routines and modules, usually by the developer, in the development environment.
  • Purposes: find defects where they are made and early; enable safe change; improve design and testability.
  • Late fixes are "often 100 times more expensive" than early ones, "more like 5:1" for small, noncritical systems (Boehm and Basili 2001).
  • Checklist: interface, local data, boundaries, paths, error handling.
  • Worked example: nine of ten checks passed; the unit accepted -2 backlog papers and billed Rs 500.

Test yourself

1. What is a unit, and what is unit testing? A unit is the smallest separately testable element of a program, such as a function or a class. Unit testing is the testing of individual units in isolation, normally by the developer in the development environment, to find defects in each unit's own logic.

2. State four purposes of unit testing. To find defects where they are made, in a small known place; to find them early, when fixing is cheapest; to provide tests that can be rerun after every change as regression tests; and to expose design problems, since a unit that is hard to test alone is usually doing too much.

3. What aspects of a unit should its tests check? Its interface (what it accepts and returns), its local data (its own variables), its boundaries (every edge and either side of it), its paths (every branch at least once), and its error handling (bad input refused cleanly).

4. Why are defects found by unit tests cheaper to fix than those found later? Because a failing unit test points to one small unit and one input, so diagnosis is quick, and because the defect has not yet been built upon by other code or shipped to users; Boehm and Basili found fixes after delivery often cost 100 times more than early fixes.

munotes.in186

Unit Testing: What It Is For

5. Who usually writes unit tests, and why? The developer who wrote the unit, because they know its inside best and the tests are needed as soon as the unit exists, long before an independent test team sees the code.

munotes.in187

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.

Issue
Done!