DEV Community

Alex Chen
Alex Chen

Posted on

"Learn Property-Based Thinking With a Tiny String Normalizer"

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()
Enter fullscreen mode Exit fullscreen mode

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)