from math import isclose
x = 1.0
for _ in range(5):
x = 0.5 * (x + 2.0 / x)
isclose(x*x, 2.0, rel_tol=0.0, abs_tol=1e-12)True
After completing this unit, you will be able to:
Local prerequisites: Units 1–5, functions, finite sums, quadratic equations, and the concept of a domain. This unit requires only the Python standard library.
An assertion states a condition expected to hold at a particular point in execution. In unittest tests, expressions such as self.assertEqual(actual, expected) record a failure as a test result. Python’s built-in assert statement is better suited to internal invariants whose failure indicates a programming error.
def triangular_iterative(n):
if isinstance(n, bool) or not isinstance(n, int):
raise TypeError("n must be an integer")
if n < 0:
raise ValueError("n must be nonnegative")
total = 0
for k in range(1, n + 1):
previous = total
total += k
assert total >= previous
return totalDo not use assert n >= 0 as the sole validation of public input. Python can remove assert statements when run with optimization enabled. Preconditions on user input must be checked explicitly and produce documented error types.
An example test pairs a particular input with a known output:
| 0 | 0 |
| 1 | 1 |
| 2 | 3 |
| 10 | 55 |
| 100 | 5,050 |
Examples are easy to read and especially useful for important cases or previously discovered errors. A few examples, however, do not explain the structure of the entire domain.
A property test checks a relation that should hold for many inputs. If , then
and
The second relation does not determine one output from scratch; it relates several function calls. Such a relation is called metamorphic and is useful when the correct output for each input is difficult to calculate directly.
Testing a property at a thousand fixed values is still a finite check. It covers more ground than five examples, but it is not a universal proof.
These three labels answer different questions.
| Level | Focus | Unit 8 example |
|---|---|---|
| unit | one function or small contract | triangular_iterative(10) == 55 |
| integration | several parts working together | the oracle, properties, and JSON writer produce a valid report |
| regression | a particular error does not recur | the boundary value n=0 still produces 0 |
A regression test is usually added after an error has been found and fixed. It should preserve the smallest input that triggers the error and the correct result. Calling every large test a “regression” obscures the failure history that the test is actually intended to guard against.
An oracle is a source for deciding what the correct output is. Options include:
An oracle that repeats production code under a different name is not independent. The same error can exist in both places while every test passes.
The implementation tested in this unit adds the integers from 1 to n in a loop. Its main oracle is derived by pairing terms. Write the sum in forward and reverse order:
Adding the two lines gives pairs, each with value . Thus
The reference function uses this formula, not a second loop. Agreement between the two is therefore more informative than a comparison of two copies of the same loop.
If the result is an exact integer or rational number, use exact equality. triangular_iterative(10) must equal 55, not merely be close to it. A tolerance in such a test can hide an off-by-one error.
For a floating-point approximation, specify the quantity being checked and justify its tolerance. Newton iteration for produces an approximation to the square root of two. The Unit 8 test checks the residual
after five iterations starting from .
from math import isclose
x = 1.0
for _ in range(5):
x = 0.5 * (x + 2.0 / x)
isclose(x*x, 2.0, rel_tol=0.0, abs_tol=1e-12)True
The tolerance is applied to a residual with a clear meaning. Do not choose a tolerance after seeing a failure merely to make the test turn green.
The summation function in this unit has the nonnegative integers as its domain. Tests should check at least:
n=0;n=1;n=10;True, because bool is a subclass of int in Python but is not an input intended by this mathematical contract.Error tests need to check the type of failure, not merely that “something failed.” A ValueError for a negative integer and a TypeError for 2.5 express two different contract violations.
A simple loop invariant is that the sum does not decrease after the next positive integer is added. Metamorphic relations compare different calls:
for n in range(201):
value = triangular_iterative(n)
assert triangular_iterative(n + 1) - value == n + 1
assert triangular_iterative(2*n) == 2*value + n*nMetamorphic relations do not require a table of 201 answers, but they still require a derivation. For the doubling relation,
If a relation is formulated incorrectly, the test can reject a correct implementation. Test code needs review just as production code does.
Validation asks whether the program answers the intended question, not merely whether it is consistent with itself. In this unit:
These different layers reduce the risk of a single error passing unnoticed. They do not make validation perfect: the specification may be wrong, the oracle may have been derived incorrectly, or the test domain may miss a defect.
Run from the project root:
python source/code/unit08_validation.py
The report contains reference values, comparisons with the pairing formula, property violations, boundary and domain checks, numerical residuals, test levels, and limits on the conclusions. The JSON uses UTF-8, sorted keys, fixed indentation, and LF line endings. core_sha256 binds the core contents before the hash field is added. This execution path uses no current time, network access, or random numbers.
Two byte-identical outputs show that two executions recorded the same results under the same conditions. This does not establish that the oracle or specification is necessarily correct.
Coverage has several meanings. Line coverage says which parts of the code were executed. Branch coverage says which control-flow alternatives were taken. Domain coverage says which mathematical inputs were checked. Having 100% line coverage does not mean that every value or property has been tested.
If the program agrees with the pairing formula for 0 <= n <= 1000, the justified conclusion is that no mismatch was found on that finite domain in the execution environment used. The proof of the pairing formula applies to all nonnegative integers because it treats generally and pairs the terms. A proof that the loop implements the sum can use a loop invariant. Tests support the claim that the concrete implementation agrees with the argument in the cases checked; tests alone do not replace either proof.
For each of the following claims, choose exact equality or a comparison with a tolerance and explain your choice: (a) the sum 1+...+100, (b) a numerical approximation to , and (c) the length of an output list.
Ask whether the representation and the specified result are exact.
Use exact equality for (a), namely 5050, because Python integer operations in this range are exact. Use a comparison with a tolerance for (b), deriving the tolerance from a residual or the requirements of the problem. Use exact equality for (c), because a list length is an integer. A tolerance for the length could accept a list with the wrong number of elements.
You are testing a function that calculates with a loop. Derive an oracle that does not use the same loop and state its domain.
Draw a square that gains one L-shaped border at each step.
The sum of the first odd numbers is . A square of size gains new points to become an square. The oracle can therefore calculate n*n for a nonnegative integer n. Compare the loop implementation with that formula, not with a copy of the loop.
For the function , write two metamorphic relations that can be tested without a complete table of values. Explain why the relations are valid.
Compare with , then try the input .
Two relations are and . The first follows from ; the second follows from . They can detect different classes of error, but boundary examples and direct checks against some reference values are still needed.
A mean function once divided by zero when given an empty list, although its contract required it to reject that input with ValueError. Write the core of its regression test and explain why the test should be retained.
Use with self.assertRaises(ValueError):.
The core is with self.assertRaises(ValueError): mean([]). Retain the test because it preserves the smallest input that once violated the contract and the behavior now required. If a later refactor returns NaN or allows ZeroDivisionError to escape, the test shows that the public behavior has changed.
All tests pass, every line of code is covered, and a million random inputs reveal no failure. State the strongest justified conclusion and give two reasons why this is not yet a general proof.
Distinguish execution coverage from domain coverage, and examine the oracle.
The strongest conclusion is that the implementation passed the stated tests, coverage checks, and samples in that environment. This is not a general proof because a million samples do not exhaust an infinite domain, and the oracle or specification may share errors with the implementation. A general proof requires an argument covering the entire domain; further validation requires independent oracles, boundary cases, properties, and environment records.