Testing Strategies
Testing Strategies
Testing ensures software works correctly, meets requirements, and handles edge cases. A-Level Computer Science requires understanding of different testing types, when each is used, and how they fit into the development process.
Why Test?
- Find bugs before users do
- Verify the software meets its requirements
- Validate the software does what users actually need
- Prevent regressions — ensure new changes don't break existing features
- Document expected behaviour (tests serve as executable specifications)
Levels of Testing
Testing is organised in a hierarchy, from smallest to largest scope:
| Level | What is tested | Who tests | When |
|---|---|---|---|
| Unit testing | Individual functions/methods | Developers | During coding |
| Integration testing | Modules working together | Developers | After unit testing |
| System testing | Complete system | QA team | Before release |
| Acceptance testing | System meets user needs | Client/users | Before deployment |
Unit Testing
Tests individual functions, methods, or classes in isolation.
Characteristics:
- Written by the developer who wrote the code
- Should be fast (milliseconds each) and independent (no external dependencies)
- Typically automated (run on every code change)
- Follow the Arrange-Act-Assert pattern
Example:
def test_add():
# Arrange
a, b = 3, 5
# Act
result = add(a, b)
# Assert
assert result == 8
Benefits: Fast feedback, easy to pinpoint bugs, enables refactoring with confidence.
Integration Testing
Tests how modules interact with each other.
Types:
| Approach | Description |
|---|---|
| Top-down | Start with high-level modules, use stubs for lower-level ones |
| Bottom-up | Start with low-level modules, use drivers to call them |
| Big bang | Integrate all modules at once (risky — hard to isolate bugs) |
| Sandwich | Combine top-down and bottom-up |
Stubs = dummy modules that simulate lower-level modules (return hardcoded values).
Drivers = dummy modules that call the module being tested (simulate higher-level modules).
System Testing
Tests the complete, integrated system against the specification.
Types of system testing:
- Functional testing: Does it do what the spec says?
- Performance testing: Is it fast enough under load?
- Security testing: Can it resist attacks?
- Usability testing: Is it easy to use?
- Compatibility testing: Does it work on different platforms/browsers?
- Recovery testing: Does it recover gracefully from failures?
Acceptance Testing
The client or end users verify the system meets their needs.
| Type | Description |
|---|---|
| Alpha testing | Done in-house by internal staff (controlled environment) |
| Beta testing | Done by real users in their own environment (before final release) |
| User acceptance testing (UAT) | Formal sign-off by the client that the system is satisfactory |
Test Design Techniques
Black-Box Testing
Tests the system without knowledge of internal code. Tests are based on the specification.
Techniques:
- Equivalence partitioning: Divide inputs into groups (partitions) that should behave the same; test one value from each
- For a field accepting ages 0-120: partitions are {<0}, {0-120}, {>120}
- Test values: -1, 50, 121
- Boundary value analysis: Test at the edges of partitions (where bugs most commonly occur)
- Test values: -1, 0, 1, 119, 120, 121
White-Box Testing
Tests the system with knowledge of internal code structure. Tests aim to cover all code paths.
Techniques:
- Statement coverage: Every line of code is executed at least once
- Branch coverage: Every branch (if/else) is taken at least once
- Path coverage: Every possible route through the code is tested (often impractical for complex code)
Test Data Types
| Type | Description | Example (for age field 0-120) |
|---|---|---|
| Normal data | Typical, expected input | 25, 50, 80 |
| Boundary data | Values at the edges of valid ranges | 0, 120 |
| Erroneous data | Invalid input that should be rejected | -5, 200, "abc" |
Test-Driven Development (TDD)
A practice where you write the test before writing the code:
1. Red: Write a failing test for the desired feature
2. Green: Write the minimum code to make the test pass
3. Refactor: Clean up the code while keeping tests passing
4. Repeat
Benefits: Forces clear thinking about requirements, produces well-tested code, enables confident refactoring.
Regression Testing
Re-running existing tests after code changes to ensure nothing is broken. Typically automated and run as part of continuous integration (CI).
Why needed: A fix in one part of the codebase can unintentionally break another part (a "regression").
Debugging vs Testing
| Testing | Debugging |
|---|---|
| Finding that a bug exists | Finding where and why the bug exists |
| Can be automated | Often manual and investigative |
| Planned activity | Reactive activity |
| Techniques: unit tests, integration tests | Techniques: breakpoints, print statements, step-through |
Debugging Techniques
- Print statements / logging: Output variable values at key points
- Breakpoints and stepping: Pause execution and inspect state line by line (using an IDE debugger)
- Watch variables: Monitor how specific variables change during execution
- Call stack inspection: See the chain of function calls that led to the current point
- Rubber duck debugging: Explain the code to someone (or a rubber duck) — the act of explaining often reveals the bug
Exam Tips
- Know the four levels of testing (unit → integration → system → acceptance) and when each is used
- Black-box tests the specification; white-box tests the code — learn specific techniques for each
- Always give three types of test data (normal, boundary, erroneous) with specific values when asked
- TDD follows the Red-Green-Refactor cycle — be able to describe each step
- Integration testing techniques (top-down, bottom-up) require you to know what stubs and drivers are
- For any given scenario, be able to recommend appropriate testing levels and techniques with justification
- Alpha testing = in-house; beta testing = external users — a simple but commonly tested distinction