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 an Individual Module
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.
White-box Testing
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:
- The sensor is not working.
- The sensor works and the level is at least 4.8.
- The sensor works, the level is below 4.8 and at least 3.1.
- 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" |
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.
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
- Identify each decision.
- List the possible outcome of each decision.
- Combine outcomes into feasible routes.
- Select input values that satisfy each routeβs conditions.
- Predict the result for each route.
- Run the module and record the actual result.
- Investigate any route whose result differs from the expectation.
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. |
Stub Testing
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.
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
Integration Testing
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 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. |
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.
-
Test
ClassifyFloodRiskin isolation. Use data for every return path. -
Connect
ReadObservation. Check that its output values arrive in the correct format. - Add a storage stub. Confirm that the correct station, level and risk are passed.
- Replace the storage stub. Check both successful and failed storage results.
- Add the alert stub. Confirm that it is called only for the evacuation path.
- Replace the alert stub. Retest the complete communication path.
- Add the display module. Check that every risk value has a recognised display result.
- Repeat previous tests. Check that adding a new module has not broken an earlier connection.
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.
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.
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.
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
- Define white-box testing.
- Explain the difference between statement and branch coverage.
- Explain why complete path coverage may be difficult for a program containing loops.
- Explain why an individual module may be tested in isolation.
- Define a stub and give two behaviours a stub may provide.
- Explain one limitation of stub testing.
- Define integration testing.
- Give four faults that integration testing may expose.
- 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
- Identify every feasible return path.
- Select one test for each path.
- State the expected return value for every test.
- Explain whether the selected tests provide branch coverage.
- 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.
- Write a suitable header for each stub.
- State what each stub should record.
- Suggest one fixed successful return value.
- Suggest one simulated failure result.
- 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.
- Explain why the fault appears during integration.
- Identify the interface assumption that differs.
- Suggest a test that would expose the problem.
- Suggest how the interface should be clarified.
- Explain which earlier tests should be repeated after correction.
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. |