Refactoring changes an implementation while preserving the behavior promised by its existing input and failure contract.
Python refactoring: preserve rejected inputs as well as accepted results
Operation contract
The original shipping charge chooses a bounded surcharge with branches. The replacement uses a lookup indexed only after the same type and range checks. A small unittest suite compares accepted inputs and confirms that both versions reject booleans, negatives and quantities outside the budget. Rewriting one arithmetic expression is not enough when callers depend on rejection behavior.
Failure and ownership boundary
The tests compare two implementations, so a shared misconception can survive both. Independent expected cases establish the boundary amounts; equivalence checks supplement those cases rather than replace them. Exception messages are not part of this stated contract. Python unittest: test rejected input and preserve state after failure and Python mutation testing: prove a suite rejects selected wrong implementations help assess what a suite actually protects.
Working program
import io
import unittest
def old_charge(quantity):
if type(quantity) is not int or not 0 <= quantity <= 2:
raise ValueError("quantity rejected")
if quantity == 0: return 0
if quantity == 1: return 25
return 40
def new_charge(quantity):
if type(quantity) is not int or not 0 <= quantity <= 2:
raise ValueError("quantity rejected")
return (0, 25, 40)[quantity]
class ChargeContract(unittest.TestCase):
def test_accepted(self):
for quantity, expected in ((0, 0), (1, 25), (2, 40)):
self.assertEqual(old_charge(quantity), expected)
self.assertEqual(new_charge(quantity), expected)
def test_rejected(self):
for operation in (old_charge, new_charge):
for quantity in (True, -1, 3, "1"):
with self.assertRaises(ValueError): operation(quantity)
result = unittest.TextTestRunner(stream=io.StringIO()).run(unittest.defaultTestLoader.loadTestsFromTestCase(ChargeContract))
print("contract suites:", result.testsRun)
print("preserved:", result.wasSuccessful())Output
contract suites: 2
preserved: TrueCosts and limits
The implementations perform bounded work for this three-value domain. Exhaustive accepted-domain checks are cheap here; a large state space requires targeted cases and independent properties rather than assuming pairwise equality proves correctness.
Common Mistakes
- Preserve the rejected-input domain when changing representation.
- Two equal implementations can share the same bug.
Connected lessons
Python unittest: test rejected input and preserve state after failure, Hypothesis property tests: compare generated cases with an independent contract, Python mutation testing: prove a suite rejects selected wrong implementations.
