25-Comp-A6 Software Engineering · May 2016
Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)
Paper: 98-Comp-A6, Software Engineering — 2016-May, 3 hours, closed book, no calculators. Answer any five of the eight questions; all five count equally (20 marks each, 100 total). All eight questions are answered below.
Reference texts: Sommerville, Software Engineering (10th ed., Pearson) — software process models, object-oriented and function-oriented design, real-time systems, software testing, formal methods, rapid software development, client-server/distributed architectures; Pressman, Software Engineering: A Practitioner's Approach (9th ed.) — supplementary process/testing coverage; IEEE/ISO 12207 — software life-cycle processes; SWEBOK — body-of-knowledge cross-reference.
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.
Functional (black-box) testing derives test cases purely from the specification, without reference to the program's internal structure: the tester picks inputs (typically using equivalence partitioning and boundary-value analysis) that exercise the documented behaviour and checks the outputs against expected results. Structural (white-box) testing derives test cases from the program's internal structure — its control-flow graph — aiming to exercise specific code elements (statements, branches, or paths) that a black-box test suite might never reach, since it is derived without knowledge of which inputs actually traverse which code paths.
The two are complementary rather than substitutes: functional testing confirms the system does what the specification says it should, but says nothing about code paths the specification never anticipated (an unhandled error branch, say); structural testing can guarantee a coverage criterion (e.g. every branch executed at least once) but a path being executed and producing the correct result are different things — structural testing alone cannot tell you what the correct result should be. The standard defect-testing process therefore uses functional testing first to derive a specification-based test suite, then measures the structural coverage that suite achieves and adds further, structurally-motivated test cases to exercise any statements or branches the functional suite missed (a common target is 100% branch coverage for safety- or business-critical code).
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.
Question 3's design has four sensor subclasses feeding one coordinating BaseStation, which drives a local AlarmSounder and a remote, network-dependent CentralMonitoringLink — that structure drives a four-level strategy escalating from isolated unit tests to full system scenarios:
| Level | Target | Technique | Example |
|---|---|---|---|
| Unit | Each Sensor subclass, BaseStation's arm/disarm/toggle logic | Structural (branch coverage) with stubbed sensor input | WaterSensor.checkState() correctly reports triggered/not-triggered for readings above/at/below its conductivity threshold |
| Integration | Sensor → BaseStation → AlarmSounder / CentralMonitoringLink chains | Functional, equivalence-partitioned by sensor type and armed state | a SmokeSensor trigger reaches AlarmSounder and CentralMonitoringLink even while the system is disarmed; a MotionDetector trigger while disarmed reaches neither |
| System | Whole security system against realistic scenarios | Scenario/use-case based, black-box | arm system, open a door, disarm within the entry delay (no alarm); arm, trigger a disabled motion detector (no alarm, per the per-sensor toggle) |
| Non-functional | Network reliability, concurrency, timing | Fault-injection / stress tests | simulate the Internet link failing exactly as an alarm condition occurs; two sensors (e.g. a window and a motion detector) triggering within the same polling cycle |
Because the system protects both life-safety (smoke, water/flood) and security (intrusion) conditions, structural coverage is weighted more heavily than a purely business system would need — every branch of BaseStation.onSensorTriggered() should be exercised, including the always-active life-safety path that bypasses the armed/disarmed check, and the CentralMonitoringLink failure path (network unreachable during a genuine alarm), which a purely scenario-driven functional suite is easy to under-test since it requires deliberately injecting a fault rather than exercising a "happy path" scenario. Regression testing after any sensor add/remove or zone reconfiguration is also required, since BaseStation's sensor set is owner-configurable at runtime rather than fixed at build time.