A-Level Computer Science / Unit 12: Software Design, Testing and Evolution

12.3.3 Testing Code, Paths and Modules

πŸ”’ Lesson slides are available to signed-in users. Sign in

12.3.3 Testing Code, Paths and Modules

Testing can begin before the complete system exists. Individual procedures and functions can be checked in isolation, temporary stubs can stand in for unfinished modules and knowledge of the code can be used to select inputs that execute important branches and loops.

Modules that work correctly on their own must then be tested together. Integration testing checks whether their interfaces, parameter values, return values and control relationships remain correct after they are connected.

By the end of this section, you should be able to:

  • Explain why individual modules are tested before the complete system.
  • Describe white-box testing using paths, branches and code structure.
  • Select data that executes different routes through an algorithm.
  • Distinguish statement, branch and path coverage.
  • Explain the purpose and limitations of a stub.
  • Describe how integration testing exposes faults between modules.
  • Identify common parameter, return-value and sequencing faults.
  • Plan an incremental sequence for connecting tested modules.

Testing at Different Levels

A modular program can be checked at several levels. Each level provides different evidence.

Level Main focus Example question
Statement or path The internal logic of an algorithm Has the false outcome of this condition been executed?
Individual module One procedure or function in isolation Does ClassifyFloodRisk return the correct category?
Controller with stubs Calls to modules that are not yet complete Does the controller call SendAlert with the correct values?
Integrated modules Communication between connected modules Does the returned risk category have the format expected by the display module?
Complete system Overall behaviour against its specification Does the finished warning system meet the user’s requirements?
Testing a lower-level component successfully does not remove the need to test the higher-level system that uses it.
Common misconception: A module that works in isolation is not guaranteed to work correctly when another module supplies its inputs.

Testing an Individual Module

Module testing: checking one procedure, function or other component separately from the rest of the system.

Module testing is often called unit testing. The developer supplies controlled inputs directly to the module and checks its outputs, return value or changed reference parameters.

Questions to ask

  • Does the module perform its stated responsibility?
  • Does it accept the correct number and type of parameters?
  • Does it return the expected value and data type?
  • Does it handle every important internal branch?
  • Does repetition process the intended number of items?
  • Does it change only the data it is responsible for changing?
  • Does it respond safely when a parameter violates an assumption?

Example module

FUNCTION ClassifyFloodRisk(
    BYVALUE WaterLevel : REAL,
    BYVALUE SensorWorking : BOOLEAN
) RETURNS STRING

    IF SensorWorking = FALSE THEN
        RETURN "SENSOR_FAULT"
    ELSE
        IF WaterLevel >= 4.8 THEN
            RETURN "EVACUATION"
        ELSE
            IF WaterLevel >= 3.1 THEN
                RETURN "WARNING"
            ELSE
                RETURN "NORMAL"
            ENDIF
        ENDIF
    ENDIF
ENDFUNCTION

This function can be tested directly without reading a physical sensor, saving a file or sending an alert.

Exam tip: Explain that module testing isolates one component so that its internal behaviour can be checked without faults elsewhere in the program obscuring the result.

White-box Testing

White-box testing: selecting tests using knowledge of the internal statements, decisions, loops and paths in the code.

The tester examines how the module is written and chooses data that executes different parts of that code.

For ClassifyFloodRisk, the internal structure reveals four distinct return routes:

  1. The sensor is not working.
  2. The sensor works and the level is at least 4.8.
  3. The sensor works, the level is below 4.8 and at least 3.1.
  4. The sensor works and the level is below 3.1.
Test WaterLevel SensorWorking Path exercised Expected return value
1 5.4 FALSE Sensor-fault return "SENSOR_FAULT"
2 5.0 TRUE Evacuation return "EVACUATION"
3 3.7 TRUE Warning return "WARNING"
4 2.4 TRUE Normal return "NORMAL"
Common misconception: White-box testing does not merely mean that the tester is allowed to read the code. The code structure must influence the chosen tests.

Statement, Branch and Path Coverage

Coverage describes how much of the internal program structure has been exercised by a set of tests.

Coverage idea What the tests attempt to execute Limitation
Statement coverage Every executable statement at least once A decision may execute one outcome while its alternative remains untested.
Branch coverage Every outcome of each decision It may not test every possible combination of decisions.
Path coverage Different routes from entry to exit Complete path coverage may be impractical when loops create many routes.

Why statement coverage can be insufficient

IF WaterLevel >= 4.8 THEN
    Risk ← "EVACUATION"
ENDIF

CALL StoreObservation(Risk)

One test with WaterLevel = 5.0 executes every displayed statement, but it does not show what happens when the condition is false. In that case, Risk may not have been assigned.

Exam tip: Use precise wording. Statement coverage concerns statements; branch coverage concerns the true and false outcomes of decisions.
Important: High coverage increases confidence but does not prove that the code is correct or that the requirements themselves are complete.

Planning Tests from the Code Paths

A white-box tester can annotate each route through the module and then select the smallest useful set of data that reaches those routes.

Path Required conditions Suitable data
P1 SensorWorking = FALSE WaterLevel = 5.4, SensorWorking = FALSE
P2 SensorWorking = TRUE and WaterLevel >= 4.8 WaterLevel = 5.0, SensorWorking = TRUE
P3 SensorWorking = TRUE, WaterLevel < 4.8 and WaterLevel >= 3.1 WaterLevel = 3.7, SensorWorking = TRUE
P4 SensorWorking = TRUE and WaterLevel < 3.1 WaterLevel = 2.4, SensorWorking = TRUE

A path-based method

  1. Identify each decision.
  2. List the possible outcome of each decision.
  3. Combine outcomes into feasible routes.
  4. Select input values that satisfy each route’s conditions.
  5. Predict the result for each route.
  6. Run the module and record the actual result.
  7. Investigate any route whose result differs from the expectation.
Common mistake: Not every theoretical combination of conditions is a feasible path. A later condition may be reached only after a particular earlier outcome.

White-box Testing of Loops

Loops create paths according to how many times their bodies execute. Useful tests should be chosen from the loop structure.

FUNCTION CountWarningReadings(
    BYREF Readings : ARRAY OF REAL,
    BYVALUE ItemCount : INTEGER
) RETURNS INTEGER

    DECLARE Index : INTEGER
    DECLARE WarningCount : INTEGER

    Index ← 1
    WarningCount ← 0

    WHILE Index <= ItemCount DO
        IF Readings[Index] >= 3.1 THEN
            WarningCount ← WarningCount + 1
        ENDIF

        Index ← Index + 1
    ENDWHILE

    RETURN WarningCount
ENDFUNCTION
Loop situation Example data Reason
Zero iterations ItemCount = 0 Checks that the loop can be skipped safely.
One iteration Readings = [3.5], ItemCount = 1 Checks the first and only cycle.
Several iterations [2.6, 3.5, 4.2] Checks repeated updates and both outcomes of the selection.
All conditions false [2.1, 2.8, 3.0] Checks that the counter remains unchanged.
All conditions true [3.1, 4.0, 5.2] Checks that every cycle updates the counter.
Exam tip: Link loop tests to the code structure: zero cycles, one cycle and several cycles are useful because they exercise different execution paths.

Stub Testing

Stub: a temporary replacement for a module that has not yet been completed or is unavailable during a test.

A stub uses the same interface as the intended module but performs only a small, predictable action. It may:

  • display or record that it was called;
  • record the parameter values it received;
  • return a fixed test value;
  • simulate a successful result;
  • simulate a controlled failure.

Example alert stub

PROCEDURE SendEvacuationAlert(
    BYVALUE StationID : STRING,
    BYVALUE WaterLevel : REAL
)
    OUTPUT "[STUB] SendEvacuationAlert called"
    OUTPUT "Station: ", StationID
    OUTPUT "Level: ", WaterLevel
ENDPROCEDURE

This does not send a real warning. It allows the controller to be tested before the communication module has been completed.

Common misconception: Successful stub testing does not prove that the finished module works. It shows that the surrounding code attempted the expected call.

Designing a Useful Stub

A useful stub must behave predictably and preserve the intended interface.

Stub feature Purpose Flood-monitoring example
Correct header Allows the real call to be tested. Receives StationID and WaterLevel.
Visible call record Confirms that control reached the module. Outputs "SendEvacuationAlert called".
Parameter record Shows whether correct values crossed the interface. Displays the station identifier and level.
Fixed return value Allows the parent module to continue predictably. StoreObservation temporarily returns TRUE.
Simulated failure Checks the parent module’s error path. The stub returns FALSE to represent failed storage.

Function stub

FUNCTION StoreObservation(
    BYVALUE StationID : STRING,
    BYVALUE WaterLevel : REAL,
    BYVALUE Risk : STRING
) RETURNS BOOLEAN

    OUTPUT "[STUB] StoreObservation called"
    OUTPUT StationID, WaterLevel, Risk

    RETURN TRUE
ENDFUNCTION
Exam tip: State that the stub is temporary and has the same interface as the unfinished module. Explain what its fixed output or return value allows the developer to test.

Integration Testing

Integration testing: checking that modules communicate and behave correctly after they have been connected.

Two modules may pass all their isolated tests and still fail when used together. Integration testing concentrates on the interfaces between them.

Example module sequence

ReadObservation
        ↓ StationID, WaterLevel, SensorWorking

ClassifyFloodRisk
        ↓ Risk

StoreObservation
        ↓ StoredSuccessfully

SendEvacuationAlert
DisplayStatus

Integration testing checks that:

  • the correct module is called;
  • parameters are supplied in the correct order;
  • data types and formats are compatible;
  • return values are interpreted correctly;
  • calls occur in the required sequence;
  • failure results are passed to the module that must handle them;
  • shared data remains consistent.
Common misconception: Integration testing is still required when every module has already passed module testing. It examines relationships that do not exist while the modules are isolated.

Common Integration and Interface Faults

Fault Example Likely symptom
Wrong parameter order StoreObservation(WaterLevel, StationID, Risk) is used instead of StoreObservation(StationID, WaterLevel, Risk). Incorrect fields or a data-type error
Different category vocabulary One module returns "EVACUATION", while another expects "CRITICAL". The display falls into an unknown-category branch.
Return value ignored StoreObservation returns FALSE, but the controller continues as though storage succeeded. A success message is displayed after a failed save.
Incorrect call order An alert is sent before the observation has been classified. The alert contains no risk category.
Different unit assumptions One module supplies centimetres while another expects metres. Risk is classified incorrectly.
Missing exceptional path The controller does not handle a sensor-fault result. An invalid warning is sent from unreliable data.
Changed interface A child module gains a new parameter, but one caller is not updated. The call fails or uses incomplete information.
Exam tip: Use the word interface and identify the value or control signal being passed incorrectly between the modules.

Integrating Modules in Manageable Stages

Connecting all modules at once can make a fault difficult to locate. A more manageable approach is to add one tested module or small group at a time.

  1. Test ClassifyFloodRisk in isolation. Use data for every return path.
  2. Connect ReadObservation. Check that its output values arrive in the correct format.
  3. Add a storage stub. Confirm that the correct station, level and risk are passed.
  4. Replace the storage stub. Check both successful and failed storage results.
  5. Add the alert stub. Confirm that it is called only for the evacuation path.
  6. Replace the alert stub. Retest the complete communication path.
  7. Add the display module. Check that every risk value has a recognised display result.
  8. Repeat previous tests. Check that adding a new module has not broken an earlier connection.
Incremental integration makes it easier to associate a newly observed failure with the connection most recently added.
Common mistake: Do not remove the stub and assume the real module is equivalent. Repeat the relevant interface and control-flow tests after replacement.

Worked Example: Coastal Flood-Monitoring System

A coastal station records a water level and sensor status. The controller classifies the risk, stores the observation, sends an evacuation alert where required and displays the result.

Controlling procedure

PROCEDURE ProcessObservation(
    BYVALUE StationID : STRING,
    BYVALUE WaterLevel : REAL,
    BYVALUE SensorWorking : BOOLEAN
)
    DECLARE Risk : STRING
    DECLARE StoredSuccessfully : BOOLEAN

    Risk ← ClassifyFloodRisk(
        WaterLevel,
        SensorWorking
    )

    StoredSuccessfully ← StoreObservation(
        StationID,
        WaterLevel,
        Risk
    )

    IF StoredSuccessfully = FALSE THEN
        CALL DisplayStorageFailure(StationID)
    ELSE
        IF Risk = "EVACUATION" THEN
            CALL SendEvacuationAlert(
                StationID,
                WaterLevel
            )
        ENDIF

        CALL DisplayStatus(
            StationID,
            Risk
        )
    ENDIF
ENDPROCEDURE

Stage 1: Test the classification module

Water level Sensor working Expected return value Path
5.4 FALSE "SENSOR_FAULT" P1
5.0 TRUE "EVACUATION" P2
3.7 TRUE "WARNING" P3
2.4 TRUE "NORMAL" P4

Stage 2: Use storage and alert stubs

FUNCTION StoreObservation(
    BYVALUE StationID : STRING,
    BYVALUE WaterLevel : REAL,
    BYVALUE Risk : STRING
) RETURNS BOOLEAN

    OUTPUT "[STUB STORE]"
    OUTPUT StationID, WaterLevel, Risk

    RETURN TRUE
ENDFUNCTION
PROCEDURE SendEvacuationAlert(
    BYVALUE StationID : STRING,
    BYVALUE WaterLevel : REAL
)
    OUTPUT "[STUB ALERT]"
    OUTPUT StationID, WaterLevel
ENDPROCEDURE

With StationID = "C-17", WaterLevel = 5.0 and a working sensor, the expected call sequence is:

ClassifyFloodRisk
StoreObservation("C-17", 5.0, "EVACUATION")
SendEvacuationAlert("C-17", 5.0)
DisplayStatus("C-17", "EVACUATION")

Stage 3: Simulate failed storage

Change the storage stub so that it returns FALSE. The controller should call DisplayStorageFailure and should not send an alert or display a successfully stored status.

Stage 4: Replace the stubs

Connect the real storage module first and rerun the successful and failed storage tests. Then replace the alert stub and repeat the evacuation path.

Exam tip: A strong integration explanation identifies the connected modules, the values passed between them and the fault that the test is intended to expose.

Recording Test Evidence

Even when the focus is code paths or module interaction, the test should record an expected result and the actual result.

Evidence field Example
Component or connection ProcessObservation β†’ SendEvacuationAlert
Purpose Check that an alert is called only for an evacuation result.
Input "C-17", 5.0, TRUE
Path expected Working sensor β†’ evacuation branch β†’ successful storage β†’ alert call
Expected result Alert stub called once with "C-17" and 5.0.
Actual result Recorded when the test is performed.
Outcome Pass or fail, with a fault reference where appropriate.

Formal test strategies, complete test plans and normal, abnormal and boundary data are developed in 12.3.5.

Common misconception: Code coverage records what was executed. It does not by itself show whether the result was correct. Expected and actual behaviour must still be compared.

Interactive: Code Path and Module Test Lab

Switch between white-box path planning, stub testing and integration testing. For each stage, choose the evidence or test action that best matches the code.

Testing focus

White-box path planner

Use the internal decisions to select data for every return path.

FUNCTION ClassifyFloodRisk(
    WaterLevel,
    SensorWorking
) RETURNS STRING

    IF SensorWorking = FALSE THEN
        RETURN "SENSOR_FAULT"
    ELSE
        IF WaterLevel >= 4.8 THEN
            RETURN "EVACUATION"
        ELSE
            IF WaterLevel >= 3.1 THEN
                RETURN "WARNING"
            ELSE
                RETURN "NORMAL"
            ENDIF
        ENDIF
    ENDIF
ENDFUNCTION

Challenge 1 of 6

Identify the feasible paths

How many different return routes are present in the function?

Focus: decisions, outcomes and return statements

Common Mistakes and Misconceptions

  • Choosing white-box data without examining the code. The statements, conditions and loops should determine the tests.
  • Confusing statement and branch coverage. Executing every statement does not necessarily execute every decision outcome.
  • Claiming that full coverage proves correctness. Coverage shows execution, not compliance with every requirement.
  • Testing only the middle loop case. Zero, one and several iterations may follow different paths.
  • Treating a stub as the finished module. A stub gives predictable temporary behaviour.
  • Using a stub with a different interface. The parent call cannot be tested accurately if the header is different.
  • Assuming isolated module tests replace integration tests. Interface faults appear only after modules communicate.
  • Checking only parameter values. Return formats, call sequence and failure signals must also be examined.
  • Connecting every module at once. The source of a new failure becomes harder to locate.
  • Failing to retest after replacing a stub. The real module may behave differently from the temporary replacement.

Practice

Core questions

  1. Define white-box testing.
  2. Explain the difference between statement and branch coverage.
  3. Explain why complete path coverage may be difficult for a program containing loops.
  4. Explain why an individual module may be tested in isolation.
  5. Define a stub and give two behaviours a stub may provide.
  6. Explain one limitation of stub testing.
  7. Define integration testing.
  8. Give four faults that integration testing may expose.
  9. Explain why modules can fail together even when they pass separate tests.

Scenario A: Air-Quality Classification

FUNCTION AirQualityBand(
    BYVALUE Reading : INTEGER,
    BYVALUE SensorValid : BOOLEAN
) RETURNS STRING

    IF SensorValid = FALSE THEN
        RETURN "INVALID"
    ELSE
        IF Reading > 150 THEN
            RETURN "HIGH"
        ELSE
            IF Reading > 70 THEN
                RETURN "MODERATE"
            ELSE
                RETURN "LOW"
            ENDIF
        ENDIF
    ENDIF
ENDFUNCTION
  1. Identify every feasible return path.
  2. Select one test for each path.
  3. State the expected return value for every test.
  4. Explain whether the selected tests provide branch coverage.
  5. Explain why the tests do not prove that the requirements are correct.

Scenario B: Workshop Reservation Stubs

A controller is complete, but the modules that save a reservation and send a confirmation message have not been implemented.

  1. Write a suitable header for each stub.
  2. State what each stub should record.
  3. Suggest one fixed successful return value.
  4. Suggest one simulated failure result.
  5. Explain what the stub tests can and cannot prove.

Scenario C: Integration Fault

CalculateCost returns a value in cents. The display module assumes that the returned number is already in dollars. Both modules pass their isolated tests.

  1. Explain why the fault appears during integration.
  2. Identify the interface assumption that differs.
  3. Suggest a test that would expose the problem.
  4. Suggest how the interface should be clarified.
  5. Explain which earlier tests should be repeated after correction.
Challenge: Design an incremental integration order for five modules. State where a stub would be useful and what evidence each stage should produce.

Review

Prompt A strong response should include
Module testing One procedure or function is checked in isolation.
White-box testing Tests are selected from statements, branches, loops and paths in the code.
Statement coverage Every executable statement is run at least once.
Branch coverage Every outcome of each decision is exercised.
Path coverage Different routes from module entry to exit are exercised.
Stub A temporary module with the intended interface and predictable behaviour.
Stub limitation It does not test the completed child module’s real behaviour.
Integration testing Connected modules and the values passed across their interfaces are checked.
Incremental integration Modules are added gradually so new failures are easier to locate.
Final exam tip: Use the chain code structure β†’ selected path β†’ expected result β†’ module interface β†’ integrated behaviour.