
Start with a product rule
Suppose shipping becomes free at a stated order amount. Testing a much larger order exercises the free path but says nothing about the threshold itself. Using greater-than instead of greater-than-or-equal can affect precisely the customer on that boundary. Write the rule as independently calculated examples before selecting a testing tool.
pytest parametrize runs one test with multiple input sets and reports each case separately. The sample covers zero, immediately below the threshold, the threshold itself and a larger amount. Negative amounts have a separate rejection test. This avoids treating malformed input as an ordinary paid order.
Choose and name boundary cases
Integer currency amounts are an explicit teaching assumption. A real application must define its currency unit and conversion rules. Fractional calculations may require Decimal and a stated rounding policy. Expected values should come from the agreed business rule; reproducing the implementation’s formula in a test can duplicate its mistake.
Names such as at-threshold are useful in CI failure reports. Keep cases independent: mutating a shared parameter object can influence a later test. Use temporary paths for files and controllable time inputs for time-sensitive behavior. Depending on the real clock or test order creates avoidable intermittent failures.
To evaluate this small suite, temporarily replace >= with > and confirm that the boundary case fails. Restore the correct implementation afterwards. This controlled exercise shows why a green result alone is insufficient: the test must detect a relevant defect. Perform it in a teaching copy or temporary branch.
Code example and verification
The notes state the prerequisites and expected outcome. Run the example in a test environment, then adapt it to your project’s data and constraints.
1. Python and pytest; save as test_shipping.py
import pytest
def shipping_cost(amount):
if amount < 0:
raise ValueError("amount must be non-negative")
return 0 if amount >= 1_000_000 else 50_000
@pytest.mark.parametrize("amount, expected", [
pytest.param(0, 50_000, id="zero"),
pytest.param(999_999, 50_000, id="below-threshold"),
pytest.param(1_000_000, 0, id="at-threshold"),
pytest.param(1_000_001, 0, id="above-threshold"),
])
def test_shipping_cost(amount, expected):
assert shipping_cost(amount) == expected
def test_negative_amount():
with pytest.raises(ValueError):
shipping_cost(-1)Install pytest with python -m pip install pytest, then run python -m pytest -q test_shipping.py. Expect 5 passed. The example assumes integer amounts in one agreed currency unit and leaves input-type validation to its caller.
2. Put the product rule in shipping.py
For application reuse, save the rule in shipping.py. This version validates type as well as value. bool is an int subclass in Python, but True is not a meaningful order amount and is rejected explicitly.
# shipping.py
FREE_FROM = 1_000_000
STANDARD_FEE = 50_000
def shipping_cost(amount):
if isinstance(amount, bool) or not isinstance(amount, int):
raise TypeError("amount must be an integer currency value")
if amount < 0:
raise ValueError("amount must be non-negative")
return 0 if amount >= FREE_FROM else STANDARD_FEEThe contract remains integer amounts in one stated currency unit. Change implementation and tests deliberately if fractional values or another unit are required. Do not derive every test expectation from these same constants; that can conceal a mistaken threshold change.
3. Test boundaries, invalid types and negative values
Place this file beside shipping.py. Expectations come from the product rule. Invalid cases deliberately reject implicit conversion of strings or booleans into money. pytest reports each input separately.
# test_shipping_contract.py
import pytest
from shipping import shipping_cost
@pytest.mark.parametrize("amount, expected", [
(0, 50_000), (999_999, 50_000),
(1_000_000, 0), (1_000_001, 0),
])
def test_boundary_contract(amount, expected):
assert shipping_cost(amount) == expected
@pytest.mark.parametrize("amount", [None, "1000000", True, 1.5])
def test_rejects_non_integer_values(amount):
with pytest.raises(TypeError):
shipping_cost(amount)
@pytest.mark.parametrize("amount", [-1, -100_000])
def test_rejects_negative_values(amount):
with pytest.raises(ValueError):
shipping_cost(amount)Running this file alone should produce 10 passed. Temporarily changing >= to > must fail the exact-threshold case. Restore the implementation after the experiment.
4. Run selected tests and keep a machine-readable report
Run from the folder containing both files. python -m keeps pytest associated with the active interpreter. Real repositories should record dependency versions in their project tooling.
python -m pip install pytest
python -m pytest -q test_shipping_contract.py
python -m pytest -q test_shipping_contract.py -k boundary
python -m pytest test_shipping_contract.py --maxfail=1 --junitxml=reports/shipping.xmlThe full run checks ten cases; the boundary selection checks four. A nonzero exit status should fail CI. JUnit preserves results rather than changing the failure outcome. Do not publish sensitive fixture data in public artifacts.
Understand what the test establishes
These tests establish a local shipping rule, not payment integration, interface quality or load capacity. When the product rule changes, agree on the new contract before rewriting assertions. A small set of risk-based examples is easier to maintain than a large collection of arbitrary numbers.
Implementation checklist
- State the currency unit and threshold contract.
- Test below, at and above the boundary.
- Define invalid-input behavior.
- Confirm a wrong comparison is detected.
References
Editorial guidance: Liyan Knowledge.



