Example tests usually ask, “Does this input produce that output?” Property-based thinking asks, “What must remain true for a whole class of inputs?” We can learn the difference with a tiny normalizer that lowercases words, removes punctuation, and collapses whitespace.
Prerequisite: Python 3.11 or later. The complete example below uses only the standard library. It is unexecuted unless you run it, so the output shown is expected output, not an observed result.
import random, string, unittest
def normalize(text: str) -> str:
kept = (c.lower() if c.isalnum() else " " for c in text)
return " ".join("".join(kept).split())
def samples(seed=7, count=1000):
rng = random.Random(seed)
alphabet = string.ascii_letters + string.digits + string.punctuation + " \t\n"
for _ in range(count):
yield "".join(rng.choice(alphabet) for _ in range(rng.randrange(0, 80)))
class Properties(unittest.TestCase):
def test_generated(self):
for raw in samples():
with self.subTest(raw=raw):
out = normalize(raw)
self.assertEqual(out, normalize(out)) # idempotent
self.assertEqual(out, out.lower()) # canonical case
self.assertNotIn(" ", out) # collapsed spaces
self.assertTrue(all(c.isalnum() or c == " " for c in out))
def test_examples(self):
self.assertEqual(normalize(" Hello, WORLD! "), "hello world")
self.assertEqual(normalize(""), "")
if __name__ == "__main__":
unittest.main()
Run python -m unittest -v normalizer_test.py. Expected success ends with OK. A failure prints the generated raw value because subTest records it. Save the seed and smallest failing input in a regression test; random generation without replayable evidence is just noise.
Try a deliberate failure
Change " ".join(...) to " ".join(...). The no-double-space property should fail for an input containing multiple surviving groups. To shrink manually, delete halves of the failing string while it still fails, then remove characters one by one. A minimal counterexample explains more than a 70-character accident.
| Property | Why it matters | Counterexample shape |
|---|---|---|
| idempotence | repeated cleanup is stable | second call changes text |
| canonical case | equivalent case maps together | uppercase survives |
| legal alphabet | punctuation cannot leak | symbol remains |
| spacing | tokens have one separator | edge/double spaces |
The isalnum() behavior includes Unicode characters, while the generator above is ASCII. That mismatch is a useful limitation: add explicit Unicode fixtures before claiming international text support. The function also loses punctuation meaning, is not suitable for identifiers or security canonicalization, and says nothing about linguistic tokenization.
In a disposable learning session, export the source, failing seed, test output, and SHA-256 hashes. Then delete preview and workspace and verify no temporary resource remains. If setup fails, keep the error log but do not keep increasing permissions.
A bounded learning environment
The current official description calls MonkeyCode’s overseas hosted choice “Free to start,” with integrated models and managed server-side cloud development environments supporting build, test, and preview. This can remove setup friction for the lesson, but it does not say every model or server costs nothing. Model availability, server quotas, regions, duration, future pricing, and terms may change, so read the current console first.
After saving the local exercise, the official campaign route for trying the cloud session is https://ly.cyberserval.tech/iIETXiF
At the end, export code, seed, counterexample, output, and hashes; then delete its preview and workspace. If the session cannot explain its limits or confirm deletion, preserve the learning files and leave.
Disclosure: This article promotes MonkeyCode using an official campaign link. I’m a MonkeyCode user, not affiliated with the project, and I receive no commission from this link.
AI assistance disclosure: This article was drafted with AI assistance and reviewed against the cited project materials.
Top comments (0)