2IRR00 · Topic 04
Quality, Robustness & Testing
Contracts, exceptions, equivalence classes, TDD, clean code
Bugs, defects & quality
A defect is code that may cause a fault; it can cause an infection (wrong state) that propagates and finally becomes an observable failure (the bug).
Chain: defect → infection → propagation → failure. No real software is bug-free.
Verification = building the product right; Validation = building the right product. Formal verification (correctness by construction) is ideal but generally infeasible for whole real systems.
So we improve quality with: good design (avoid defects), quality assurance (find defects), testing (find failures), debugging (localise/fix).
Quality techniques in this course: robustness, exception handling, unit testing, TDD, pair programming / code reviews.
Common mistakes
- Confusing verification (right-built) with validation (right-product).
Exam tips
- Dijkstra: testing shows the PRESENCE of bugs, never their ABSENCE.
- Reviewing finds defects without running code; testing finds failures by running code.
Bugs, defects & quality practice
1 questions
Robustness & contracts (design by contract)
Robustness is the ability of a system to cope with errors and erroneous input. Design by contract specifies obligations with preconditions, postconditions, and invariants (informally via JavaDoc here).
Precondition (@pre): must hold BEFORE execution; an obligation on the CLIENT — violating it is the client's fault.
Postcondition (@post): must hold AFTER execution; an obligation on the METHOD/provider — violating it is a bug.
Invariant: a class condition true whenever the object is accessible (at all times).
Contract keywords for 2IRR00: @param, @pre, @post, @return, @throws. Be precise but not fully formal — natural language over confusing notation.
A method is robust if it ALWAYS throws an exception when its preconditions are violated.
/*
* Replaces every element of a with a[i]*b[i].
* @param a an array of int values
* @param b an array of int values
* @pre a != null && b != null && a.length == b.length // length added!
* @post (\forall i; 0<=i<a.length; a[i] == \old(a[i]) * b[i])
* @throws NullPointerException if a == null || b == null
* @throws IllegalArgumentException if a.length != b.length
*/Common mistakes
- Forgetting the a.length == b.length precondition / the @post clause.
- Putting the blame in the wrong place: a violated precondition is on the client, a violated postcondition is on the method.
Exam tips
- @pre = client's obligation; @post = method's obligation. This wording earns points.
- Robust ⇔ always throws when preconditions are violated.
- Add @param lines for partial credit when asked to fix a contract.
Robustness & contracts (design by contract) practice
1 questions
Exception handling
Exceptions signal erroneous situations and force the client to handle failures instead of encoding them in magic return values.
Declare in the contract (@throws, semantic) AND the method header (throws, syntactic), then implement the checks and throw with a meaningful message.
try/catch/finally: matching catch runs on an exception; finally always runs; unhandled exceptions crash the program (the client's responsibility if the contract documented it).
Use exceptions to: make a method robust, avoid magic values, signal special situations in non-local methods, ensure a failure can't be ignored.
Prefer alternatives when: the context is local (private methods → assertions, out of scope here) or a precondition is too expensive/impossible to check.
try { /* ... */ }
catch (Exception e) { /* ... */ } // AVOID
// Reasons:
// • Does not align with a proper method contract
// • Unclear (for clients) what is actually being handled
// Prefer specific types, or: catch (NullPointerException
// | IllegalArgumentException e)Common mistakes
- catch (Exception e) — too broad, not contract-based, hides what's happening.
- Returning -1 / magic values instead of throwing; the client can silently ignore them.
Exam tips
- Memorise the two reasons to avoid catch(Exception e): not contract-aligned + unclear to clients.
- Returning a special value (like -1) is legitimate-looking and easy to miss; an exception cannot be ignored.
Exception handling practice
1 questions
Unit testing, equivalence classes & TDD
A unit test has a purpose, controls input, observes output, and decides pass/fail against the specified result. Equivalence classes and boundaries keep the test set small but representative.
White-box: test internal logic from the code (internal state). Black-box: test intended functionality from the spec (input → expected output / specified exception).
Construct test cases via: well-defined input/output, boundaries (empty/null array, on/near limits), equivalence classes (one case per class — e.g. value in vs not in array), coverage (statement/branch/path), random (seeded), alternative implementation, consistency.
Testing exceptions: define an input that triggers it; pass only if the exception is thrown; fail if none or the wrong one. Advise: check the message is present.
TDD red-green-refactor: write failing tests for new behaviour → write code to pass → improve quality (refactor, no behaviour change). Test incrementally and re-test after changes.
Pair programming: one programmer + one reviewer at one workstation (add both as authors).
T1 Correct multiplication: arrays mixing +, -, 0 values
→ a is replaced by the products. Pass if a holds correct products.
T2 Null input: a null / b null / both null
→ expect NullPointerException. Pass if NPE is thrown.
T3 Unequal lengths: a.length != b.length
→ expect IllegalArgumentException. Pass if IAE is thrown.
(Positive, negative and zero values are ONE equivalence class — don't
waste 3 cases on them.)Common mistakes
- Picking many cases from the SAME equivalence class (e.g. several positive numbers) instead of one per class.
- Not connecting tests to the activity diagram / requirements / main use case.
- Forgetting boundary cases (empty/null).
Exam tips
- Each test answer should state: what it tests + why it differs + input + expected output + pass/fail rule.
- Java's type safety means you can't pass a String[] to int[] — so 'test with a String array' is a non-answer.
- White-box vs black-box: code-based vs spec-based.
Unit testing, equivalence classes & TDD practice
1 questions