DEV Community

Charlie Zhu
Charlie Zhu

Posted on

The Edge Where Free AI Code Breaks

Somewhere in a codebase, a function died on an empty string. The tests were green. The review was clean. The function had been generated by a model, checked against three examples, and merged. Then a user submitted a form with a blank field, and the function raised an exception that no test had ever seen.

This is the boundary-condition gap. Example-based tests prove a function works for the inputs a human thought of. They do not prove it works for the inputs a model never considered. And models, left to their own devices, tend to write code that handles the happy path beautifully and the edge path not at all.

The gap is not specific to free models. Paid models have the same blind spot. But free models make the gap easier to reach. The cost of iterating is zero, so more generated code enters the codebase, and more boundary conditions arrive uninvited.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. The workflow below uses MonkeyCode's free model access — which includes a 10-million-token allowance as of this writing — and its free server option. Token allowances and server terms change; the current details live in the project's documentation.

The fix is not more examples. The fix is a different kind of test. Property-based testing flips the question. Instead of asking "does this function return the right value for this input?", it asks "does this function preserve the right invariant for every input in this domain?"

Consider a concrete case. A model was asked to write a function that parses a date string and returns a weekday name. The model produced:

from datetime import datetime

def weekday(date_str: str) -> str:
    return datetime.strptime(date_str, "%Y-%m-%d").strftime("%A")
Enter fullscreen mode Exit fullscreen mode

Three example tests passed. "2026-08-21" returned "Friday". "2026-01-01" returned "Thursday". "2024-02-29" returned "Thursday". The function looked correct.

A property test asks a different question. For any valid date string in the supported range, the result should be one of seven weekday names. For any invalid input, the function should raise a clear error rather than crash with a stack trace. The Hypothesis library makes this trivial:

from hypothesis import given, strategies as st
from datetime import date

@given(st.dates())
def test_weekday_is_always_a_weekday(d: date):
    result = weekday(d.isoformat())
    assert result in {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}

@given(st.text())
def test_invalid_inputs_raise_cleanly(s: str):
    try:
        weekday(s)
    except ValueError:
        pass  # expected
    except Exception as exc:
        raise AssertionError(f"unexpected exception type: {type(exc).__name__}") from exc
Enter fullscreen mode Exit fullscreen mode

The first property passed. The second property failed within seconds. The model's function raised a TypeError for None, an AttributeError for empty strings, and a ValueError with a raw strptime message for malformed input. None of those failures appeared in the example tests, because nobody had written an example for "" or None.

The failure mode is instructive. The model had learned the shape of a date parser — import, call, format — without learning the contract. A human who writes a date parser from scratch usually thinks about the contract because the runtime forces the conversation. A model that generates code from patterns skips that conversation. The result is code that looks right and is right, except at the edges.

Property-based testing does not fix the model. It fixes the pipeline. The property tests become a permanent gate. Every time the model regenerates the function, the properties run. If the new version violates an invariant, the gate fails, and the human sees exactly which property broke. This turns "trust the model" into "verify the model against the contract", which is a much better position to be in.

The same idea scales beyond single functions. A small project that generates several utility functions can define a property file for each module. The properties are the real specification; the model's output is a candidate implementation. This separation is the durable pattern.

The workflow fits neatly on free compute. Generation happens through MonkeyCode's free model access. Property tests run in a plain Python environment. The whole thing can live on the free server option as a scheduled job. The resource footprint is tiny — Hypothesis generates thousands of cases, but each case is a single function call, so a few seconds of CPU covers a full property suite.

Not every project needs property tests. A script that runs once and is thrown away does not need a contract. A library that other code depends on, or a function that handles user input, absolutely does. The cost of writing properties is small. The cost of a boundary-condition crash in production is large. The asymmetry is the argument.

The empty string is patient. It waits in form fields, in config files, in API responses. Example tests will not find it, because example tests only look where the human pointed. Property tests sweep the whole floor. For generated code, that sweep is not a luxury. It is the difference between code that looks right and code that is right.

If you want to try this pattern, start with one function, write three properties, and run Hypothesis against the model's output. The properties take ten minutes to write, and they will outlive the model, the token allowance, and the server. That is the point.

Top comments (0)