Production Code vs. Non-Production Code: Why the Distinction Matters

Why separating production code from testing and experimental code shapes decisions like where integration tests should live in a repository.

It’s a small question that turns out to have real structural consequences: where should integration tests live relative to the rest of your source code? The answer depends on a distinction that’s worth making explicit — the difference between production code and non-production code.

Where this question comes from

The trigger, in one case, was deciding where to put integration tests for a monitoring component. Putting them directly under src alongside everything else creates an odd artifact: if other project folders happen to start with letters ah and jz, a folder literally named integration-tests lands somewhere awkward in the middle, alphabetically speaking. One fix is prefixing it with a special character — _integration-tests, for instance — to pin it to the start or end of the listing.

A more structural option is a dedicated parent folder for test facets — something like _test or _tests — hosting child folders like integration, performance, benchmark, and ui for different testing concerns.

There’s also a broader shift worth factoring in: newer conventions like Directory.Build.props and .editorconfig make a top-level split between src and test/tests more attractive, because it lets you apply different build settings by inheritance to production vs. non-production code. This pattern shows up in a number of modern open-source .NET codebases — OpenTelemetry .NET and ML.NET among them.

Production code vs. non-production code, defined

Production code is what actually runs where end users interact with the system. It needs to be stable, well-tested, and optimized for performance, reliability, and security. Its whole purpose is delivering functionality reliably — changes to it typically go through rigorous review and testing precisely because they affect real users and real business operations.

Non-production code covers everything used in development, testing, staging, or other non-live environments — experimental features, tests, debugging tools, mock services. Its purpose is verification: trying out changes, testing new features, debugging, load testing, all without touching the live environment. It can afford to be more flexible and less strictly optimized, and it often carries extra logging or diagnostic scaffolding that production code wouldn’t need. It still has to be reliable enough to produce meaningful results, just not to the same bar as production code.

Why the distinction is worth making explicit

  • Risk management. A clear separation reduces the chance that a bug or security issue in testing scaffolding leaks into the live environment.
  • Environment isolation. Keeping production and non-production environments isolated protects against testing activity interfering with the live system or corrupting real data.
  • Deployment processes. Production and non-production code often warrant different deployment strategies — stability matters more on one side, flexibility more on the other.

The upshot

None of this dictates one folder layout as universally correct. But once you frame the question as “is this production code or not,” the answer to where integration tests belong — and how the rest of the repository should be sliced — gets a lot easier to reason about. See the companion piece on the DevTree layout for how this plays out in practice.