| name | contract-driven-testing |
| description | Derive a three-level test pyramid directly from a function's Code Contracts (preconditions, postconditions, invariants) — contract-targeted unit tests, property-based tests, precondition-violation tests, plus the no-contracts fallback and the contract review checklist. Loaded by the code-critic agent. |
| user-invocable | false |
| version | 1.0.0 |
| category | agent-reference |
| tags | ["contracts","testing","property-based-testing","code-critic"] |
| effort | low |
Contract-Driven Testing
When a function under review carries Code Contracts (preconditions,
postconditions, invariants — see the project's ## Code Contracts
convention), derive a three-level testing pyramid directly from them. The
contracts ARE the test plan — this is a mechanical translation, not a
creative exercise.
The examples below show the same pyramid in three ecosystems (Python +
icontract/hypothesis, Rust + quickcheck/proptest, TypeScript +
fast-check). Use whichever matches the project under review; the structure
generalizes to any language with an assertion library and a property-testing
framework.
Level 1 — Contract-Targeted Unit Tests
- Read every precondition, postcondition, and invariant on the function under
test.
- Write one focused test per postcondition. Derive the test name from the
postcondition itself so the intent is legible without reading the body.
- Cover both the happy path and the boundary conditions for each
postcondition.
Python:
def test_top_k_result_length_equals_min_k_items():
assert len(top_k([1.0, 2.0, 3.0], k=2)) == 2
assert len(top_k([1.0, 2.0, 3.0], k=10)) == 3
Rust:
#[test]
fn top_k_result_length_equals_min_k_items() {
assert_eq!(top_k(&[1.0, 2.0, 3.0], 2).len(), 2);
assert_eq!(top_k(&[1.0, 2.0, 3.0], 10).len(), 3);
}
TypeScript:
test("top_k result length equals min(k, items.length)", () => {
expect(topK([1.0, 2.0, 3.0], 2)).toHaveLength(2);
expect(topK([1.0, 2.0, 3.0], 10)).toHaveLength(3);
});
Level 2 — Property-Based Tests from Contracts
- Translate each postcondition into a property assertion that holds for
every generated input, not just the hand-picked examples in Level 1.
- Encode preconditions as generator constraints — constrain what the
generator produces, don't filter/reject after the fact (rejection sampling
wastes cases and can hide precondition bugs).
- Use the property-testing framework native to the stack:
hypothesis
(Python), fast-check (TypeScript/JavaScript), quickcheck or proptest
(Rust), or the equivalent for the language under review.
Python (hypothesis):
from hypothesis import given
import hypothesis.strategies as st
@given(
items=st.lists(st.floats(allow_nan=False), min_size=1),
k=st.integers(min_value=1),
)
def test_top_k_all_postconditions(items, k):
result = top_k(items, k)
assert len(result) <= len(items)
assert len(result) == min(k, len(items))
assert all(x in items for x in result)
Rust (proptest):
proptest! {
#[test]
fn top_k_all_postconditions(
items in prop::collection::vec(any::<f64>(), 1..50),
k in 1usize..20,
) {
let result = top_k(&items, k);
prop_assert!(result.len() <= items.len());
prop_assert_eq!(result.len(), k.min(items.len()));
prop_assert!(result.iter().all(|x| items.contains(x)));
}
}
TypeScript (fast-check):
import fc from "fast-check";
test("topK satisfies all postconditions", () => {
fc.assert(
fc.property(
fc.array(fc.double(), { minLength: 1 }),
fc.integer({ min: 1 }),
(items, k) => {
const result = topK(items, k);
expect(result.length).toBeLessThanOrEqual(items.length);
expect(result.length).toBe(Math.min(k, items.length));
expect(result.every((x) => items.includes(x))).toBe(true);
},
),
);
});
Tip: where the language's contract library supports it (e.g. Python's
icontract-hypothesis), auto-generate strategies directly from the contract
decorators instead of hand-writing the generator constraints.
Level 3 — Precondition Violation Tests
- Write one negative test per distinct precondition.
- Verify the contract fails loudly with a useful message — not a silent
wrong answer, not a generic panic with no context.
- Verify that valid inputs never trigger a spurious violation (a
false-positive precondition check is its own bug).
Assert on the contract library's violation/failure type for the language
under review — icontract.ViolationError in Python, a panic!/Result::Err
from a debug_assert!/contract macro in Rust, a thrown error from the
contract wrapper in TypeScript. The structure generalizes across all three;
only the exception/assertion mechanism changes.
Python:
def test_top_k_rejects_empty_items():
with pytest.raises(icontract.ViolationError, match=r"len\(items\) > 0"):
top_k([], k=1)
def test_top_k_rejects_nonpositive_k():
with pytest.raises(icontract.ViolationError):
top_k([1.0, 2.0], k=0)
Rust:
#[test]
#[should_panic(expected = "items must be non-empty")]
fn top_k_rejects_empty_items() {
top_k(&[], 1);
}
#[test]
#[should_panic(expected = "k must be positive")]
fn top_k_rejects_nonpositive_k() {
top_k(&[1.0, 2.0], 0);
}
TypeScript:
test("topK throws on empty items", () => {
expect(() => topK([], 1)).toThrow(/items must be non-empty/);
});
test("topK throws on non-positive k", () => {
expect(() => topK([1.0, 2.0], 0)).toThrow(/k must be positive/);
});
When There Are No Contracts (Yet)
If the function under review has no explicit contracts:
- Flag it for the engineer if the function is complex or safety/security
critical — contracts should have been written alongside the
implementation, not after.
- Infer implicit contracts from the function's docstring/doc comment and its
observable behavior.
- Write property-based tests against the inferred properties anyway — the
absence of a formal contract doesn't excuse the absence of a test.
Contract Review Checklist
When reviewing contracts written by the engineer: