When Documentation Becomes a Test Suite: The Quiet Power of Writing for Humans and Machines at Once

Kai Nguyen

Hatched by Kai Nguyen

May 08, 2026

9 min read

87%

0

The real question is not whether code should be documented

Most programming advice treats documentation and testing as separate chores. First, explain the code. Then, verify it. First, write the story. Then, check the facts. But what if that separation is a mistake? What if the best documentation is not merely descriptive, and the best tests are not merely mechanical?

That is the deeper tension hiding in plain sight: should code be written to be read, or written to be proved? The answer, surprisingly, may be that good code does both. A docstring can be a promise to the reader. A doctest can be a promise to the machine. When those promises line up, code stops being a pile of instructions and becomes a living contract.

This is not just a style preference. It changes how developers think, how teams collaborate, and how quickly a small idea can become trustworthy software. The moment you realize that a short example can serve as both explanation and verification, you see documentation in a new light. It is not the polite prose wrapped around your code. It is part of the code’s executable truth.

Docstrings are not just comments, they are interface design

A lot of documentation fails because it tries to say too much or too little. A one line summary that reads like a shrug is useless. A long ramble that buries the point is worse. The discipline of writing a clear summary line, followed by a blank line, then a fuller explanation, forces an author to separate the essence from the elaboration. That structure matters because it mirrors how people actually learn: first they want the thing in one sentence, then the caveats, then the details.

There is also a subtle but important aesthetic rule in the use of triple double quotes. It sounds trivial, even ceremonial, but conventions like this reduce ambiguity. In practice, conventions do something larger than formatting. They make documentation legible at a glance. They create a shared visual language so that readers know where explanation begins and ends, and so tools can reliably find it.

This is why the distinction between a one liner and a multi line docstring is not merely about length. A one liner is for the obvious case, the code whose purpose can be named without ceremony. A longer docstring is for the code that needs context, constraints, or examples. In other words, docstrings are not prose attached after the fact. They are interface design for human understanding.

Good documentation does not repeat the code. It reveals the shape of the decision behind the code.

That idea becomes more powerful when you notice the other kinds of documentation that can exist beyond the obvious function docstring. Attribute docstrings and additional docstrings point to a broader principle: meaning can live at multiple levels. A codebase can narrate itself not only through functions, but through objects, modules, and examples. Documentation is not a single label pasted on top. It is a distributed layer of intent.

The example is the shortest form of truth

If docstrings are interface design, doctests are something rarer: documentation that refuses to stay hypothetical. A doctest asks a simple but demanding question, can your explanation survive contact with actual execution? Instead of merely asserting that a function adds numbers, you show it. Instead of describing its behavior in the abstract, you write the interaction exactly as a user would see it.

That makes examples unusually powerful. Consider a function like this:

>>> add(4.0, 2.0)
6.0
>>> add(4, 2)
6

Those two lines do more than demonstrate usage. They communicate type behavior, formatting expectations, and output conventions. They compress several paragraphs of prose into something concrete and testable. The example is not decorative. It is the shortest form of truth.

This matters because software failures often begin as mismatched assumptions. One developer assumes a function returns 6.0. Another expects 6. One expects an exception. Another expects silent conversion. A doctest makes those assumptions visible, and once visible, they can be challenged. That is why doctests are especially suited to quick automation of acceptance tests at the integration and system level. They do not replace deeper testing strategies, but they excel at preserving the agreement between intention and behavior.

The strictness of doctest is a feature, not a flaw. Human readers are forgiving, but machines are not. That difference is where many bugs hide. A docstring can say what a function is supposed to do. A doctest can prove whether the current implementation still matches that story. When the output drifts, the discrepancy is no longer abstract. It is immediate and undeniable.

The hidden advantage is not testing, it is epistemic discipline

The most interesting thing about combining documentation and testing is not productivity. It is epistemology, the question of how we know what we know. Most codebases suffer from a split brain. The comments say one thing, the tests say another, and the implementation may be doing a third. Developers then spend time reconciling artifacts that were never designed to agree.

A doctest reduces that fragmentation. It creates a single artifact that serves two audiences at once: the human who wants to understand the intent, and the machine that needs to verify it. That dual role produces a valuable discipline. You can no longer write a vague promise and walk away. You must say what the code does in a way that can be executed. You must define behavior in terms that can be checked.

This changes the quality of thought. Consider the difference between writing, “Returns the result of adding two numbers,” and writing an example that handles integers, floats, or exceptions. The example forces specificity. What happens with 4 and 2? What happens with 4.0 and 2.0? Does the function preserve type? Does it coerce? What about invalid input? In each case, the act of documenting becomes the act of deciding.

That is why the best documentation is often a byproduct of rigorous design thinking. It does not merely explain a decision. It makes the decision harder to avoid.

If a behavior cannot be shown in an example, it may not yet be clear enough to trust.

There is a deeper cultural lesson here too. Teams often treat testing as a downstream quality gate and documentation as an upstream communication task. But when they converge, the workflow becomes less linear and more conversational. You write a docstring, then you ask whether it can be demonstrated. You write an example, then you ask whether it deserves to be a contract. The codebase starts to teach its own correctness.

Small projects, large lessons

In small projects, the argument for doctests is especially compelling because the overhead is low and the payoff is immediate. When a project is tiny, explicit names, comments, and docstrings may already be enough to keep everyone aligned. But that is precisely when doctests shine: they let a small project acquire a dependable memory without building a separate apparatus around it.

Imagine a calculator module with a few helper functions. A traditional test file might live somewhere else, with its own fixtures, naming conventions, and maintenance burden. A doctest, by contrast, can sit directly beside the function it describes. The usage example becomes the test case, and the test case becomes the example. That proximity reduces friction and makes maintenance more likely. If the behavior changes, the documentation breaks in the same place the expectation breaks. The mismatch is impossible to ignore.

This is especially useful for functions that are easy to understand but easy to misuse. A function that handles rounding, string formatting, or exception behavior might look obvious from the name alone. Yet obviousness is often where bugs breed. A docstring can clarify intent. A doctest can prove edge cases. Together, they transform “seems clear” into “is clear.”

There is also a psychological effect. Developers are more likely to read examples than abstract prose, and more likely to trust examples that run. That makes doctests an unusually persuasive form of communication. They do not ask the reader to imagine correctness. They let the reader observe it.

A practical mental model: explain, exemplify, execute

The most useful way to think about this synthesis is as a three step loop.

  1. Explain the behavior in a concise docstring summary.
  2. Exemplify the behavior with concrete usage cases.
  3. Execute those examples so the documentation stays honest.

This loop turns documentation into a feedback system. The summary line gives the purpose. The paragraph below it gives nuance. The example gives proof. If the example fails, either the code changed or the explanation was wrong. In both cases, the mismatch is valuable because it exposes a hidden assumption.

You can apply this mental model even when you are not literally using doctest. For example, when writing API docs, include one or two examples that are realistic enough to serve as smoke tests. When writing internal module docs, make sure at least one example reflects a true workflow, not a contrived one. When reviewing code, ask whether the docstring says what the code does, and whether the example demonstrates the most important behavior.

A useful heuristic is this: if the explanation cannot be verified, it is still a claim, not yet a contract.

That distinction is the heart of the matter. Documentation without verification decays into folklore. Testing without explanation becomes opaque enforcement. Combined, they create a standard of clarity that is both humane and machine checkable.

Key Takeaways

  • Write docstrings as contracts, not ornaments. Start with a sharp summary line, then add the detail that a future reader will actually need.
  • Use examples to force specificity. A concrete input and output reveal more than a paragraph of abstract description.
  • Treat doctests as executable documentation. If an example matters enough to explain, it often matters enough to verify.
  • Prefer proximity between intent and behavior. Keep the explanation close to the code it describes so drift is easier to detect.
  • Use strictness as a design tool. When the example fails, it usually means the meaning was never as clear as it seemed.

The codebase as a conversation, not a museum

The deepest insight here is that software should not be treated like a static artifact. A museum exhibit can be admired from a distance, but code lives in time. It changes, accumulates interpretations, and acquires new readers who were not present at its creation. Documentation exists to carry intent across that distance. Testing exists to ensure the intent still matches reality.

When you combine them, you stop asking, “Did I remember to document this?” and start asking, “Can I make this behavior so clear that the documentation proves itself?” That shift is profound. It makes clarity a property of the system, not just a virtue of the writer.

In that sense, the best docstring is not the one that says the most. It is the one that can be believed, because it can be run. The best example is not the one that merely illustrates. It is the one that survives execution. And the best codebases are those where humans and machines can read the same sentence and agree on what it means.

That is the real payoff: not just cleaner code, but a more honest one. A codebase that documents itself through action is no longer just a collection of functions. It becomes a place where meaning is continuously checked against reality, and that may be the highest standard software can reach.

Sources

← Back to Library

Hatch New Ideas with Glasp AI 🐣

Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)

Start Hatching 🐣