25-Comp-A6 Software Engineering · December 2015
Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)
National Exams — December 2015 — 98-Comp-A6 Software Engineering. Three-hour, closed-book exam, no calculator permitted. Format: eight questions, candidates answer any five of the eight (all questions equal weight — each of the five counted questions is worth 20%; only the first five questions as they appear in the answer book are marked). All eight questions are solved below for completeness.
Reference texts: Sommerville, Software Engineering (10th ed., Pearson) — software process models, object-oriented and function-oriented design, software testing, requirements engineering, dependability and critical systems, configuration management, project/risk management; Pressman, Software Engineering: A Practitioner's Approach (9th ed.) — supplementary process and testing coverage; Leveson, Safeware: System Safety and Computers — hazard analysis and fault tree analysis for safety-critical software (applied to the Question 7 medical-device case).
Question text not reproduced: the examination questions are © Engineers and Geoscientists BC. Open the official past paper (linked at the top of this page) to read the question, then follow the worked solution below.
Black-box (functional/specification-based) testing derives test cases purely from the component's specified behaviour — its inputs, outputs and stated requirements — with no reference to how it is implemented internally. White-box (structural) testing derives test cases from the component's internal structure, aiming to exercise specific statements, branches, or paths through the code. Black-box testing's strengths are that it can be designed as soon as the specification exists (even before the code is written), that it is independent of implementation details and so survives a refactor of the internals unchanged, and that it directly checks whether the software does what it is supposed to do. Its weakness is that it cannot guarantee any particular code coverage — a path through the code may simply never be exercised by any test derived purely from the spec, and defects arising from unusual internal states invisible at the interface can be missed. White-box testing's strength is the reverse: it can systematically guarantee coverage of statements, branches or paths, catching internal logic errors, dead code, and off-by-one conditions that a black-box view would never reveal. Its weakness is that it is tied to a specific implementation (a refactor with unchanged behaviour still requires new white-box tests), it requires access to source and specialised coverage tooling, and passing every internal path proves nothing about whether the code actually implements the intended requirement — a consistently-wrong implementation can have 100% white-box coverage and still fail every black-box case.
A test run exercises the program on one specific input (or a small finite set of inputs) and observes whether the output matches what was expected; if it does not, an error has been demonstrably found. But the space of possible inputs to any non-trivial program is astronomically large or infinite, and exhaustively trying every one is not feasible in any practical amount of time. A test suite can therefore only ever sample that space. Passing every test in the suite proves that the program behaves correctly on the particular inputs tried — it says nothing about the vastly larger set of inputs that were never tried, any one of which could still trigger a latent defect. This is Dijkstra's well-known observation: testing can conclusively show a bug exists (by triggering it) but can never conclusively show that no bug exists, because "no bug found yet" and "no bug present" are not the same statement given an untested remainder of the input space.
| Test case | Purpose |
|---|---|
| Empty array | Boundary: zero elements |
| Single-element array | Boundary: trivially sorted |
| Already-sorted array | Best-case / no-op behaviour |
| Reverse-sorted array | Worst-case ordering |
| Array with duplicate values | Stability/tie-handling of equal keys |
| Array with negative and positive values | Correct ordering across sign |
| Array with all identical elements | Degenerate case, no distinct ordering |
| Array containing INT_MIN / INT_MAX | Boundary values of the integer type |
| Large random array | General-case correctness at scale |
| Test case | Purpose |
|---|---|
| Empty line | Boundary: zero-length input, expect count 0 |
| Line of all blanks | Expect count 0 |
| Line with no blanks at all | Expect count = line length |
| Line with mixed blank and non-blank characters | General case |
| Line with leading and/or trailing blanks | Blanks at the boundary of the string, not just interior |
| Line containing tab characters | Clarifies whether "blank" means space only or any whitespace |
| Line with punctuation/special characters | Confirms non-space, non-alphanumeric characters are counted as non-blank |
| Single-character line | Boundary: smallest non-empty input |
| Very long line | General-case correctness / buffer handling at scale |
| Operation | Test cases |
|---|---|
| length() | Empty string (0); single character (1); typical string; string containing special/whitespace characters |
| concatenation() | Both operands non-empty; one operand empty (identity check: s + "" = s); both operands empty (result empty); result length equals sum of operand lengths |
| sub-string selection() | Substring at the very start; substring at the very end; the whole string; a zero-length substring; a single-character substring; out-of-range indices (start beyond length, negative start, end before start) to confirm defined error behaviour |