System testing vs integration testing comes down to one variable: how much of the product is assembled when a test runs. Integration testing wires together a subset of modules and checks whether they honor their contract at the seam. System testing assembles the whole product and checks it, as a black box, against what the requirements say it should do. Because the scope of assembly differs, the two levels catch different failure classes: integration catches contract breakage, system catches requirement-level behavior failures that only surface once everything is wired together.
A build can pass every integration test in the pipeline and still ship a bug nobody caught. That gap, not a vocabulary mixup, is what this article is actually about.
This is for the engineer, SDET, or release owner staring at a build about to promote, or the one just asked to draw this exact line. It's not for a QA lead building a suite-allocation strategy, someone asking whether an AI-generated suite is testing anything real, or someone picking values for a single test case. Those live one level up or down from here.
Most write-ups of this question give you a two-column table, restate two definitions, and stop. None of them show the one drawing that actually settles it: where, on a single assembled product, integration ends and system begins.
The boundary is scope of assembly
Every product gets assembled into the same shape eventually: modules feed subsystems, subsystems feed the whole product, and the whole product talks to its real dependencies, a datastore, a payment gateway, whatever third-party service it can't function without. Integration and system testing aren't two different structures, just two different slices of that one.
Integration testing takes a tight slice: two or three modules, wired together, tested at the interface where they hand data to each other. It enters at a module boundary, a function call, an API request, a message on a queue, and it asks a narrow question: does this module honor the contract the next one expects. Everything outside that slice gets stubbed or ignored.
System testing takes the whole slice. Every module, every subsystem, every real dependency, assembled the way it actually runs in production, tested from the one entry point a real user has: the UI, or the client that stands in for it. It doesn't ask whether two neighbors agree with each other. It asks whether the assembled product does what the requirements said it would do. For the deeper treatment of that side on its own, see system testing.
One assembly, drawn once. The tight bracket marks a module pair; the wide bracket wraps the whole thing, dependencies included.
The picture above is one assembly with two brackets on it. Nothing about the structure changes between them. What changes is how much of it has to be true for the test to pass.
Two levels, two failure classes
The scope difference isn't cosmetic. It decides which class of failure each level can even see. Google's testing team draws the same line by test size rather than by name: a Large test maps to end-to-end or system testing, and a Medium test maps to two tiers communicating, what most teams call integration testing (Google Testing Blog).
Integration catches contract breakage. A module changed the shape, type, ordering, null handling, timing, or error semantics of what it hands its neighbor, and the seam breaks. The failure is local: a wrong value or a thrown error, right at the boundary between two components, usually a fast and precise fix.
System catches requirement-level behavior failures. Every seam holds. Every pairwise contract is honored exactly as written. The assembled product still does the wrong thing, because the requirement spans components that no single integration test assembles at once.
Here's a version of that failure that's easy to check by hand. A checkout flow has two services: a pricing service that computes the subtotal and converts a loyalty-point redemption into a dollar discount, and a tax service that applies the local rate to whatever subtotal it's handed. Both pass their integration tests. The pricing service's contract test confirms it turns 500 points into a $5.00 discount, correctly. The tax service's contract test confirms it returns 8.25% of a $100 subtotal as $8.25, correctly.
The requirement, stated in the spec, is that the customer is taxed on the post-discount amount: subtotal minus discount, plus tax on that discounted number. Nobody wired the discounted subtotal into the tax call. The order service passes the original $100, not $95, because that's the field it already had. Tax comes back as $8.25. The customer is charged $100 minus $5 plus $8.25: $103.25. The requirement says $102.84. Every seam is green. The system is wrong by 41 cents on every discounted order, until something checks the assembled flow instead of the seams.
Both seams hand off exactly what their contract promises, and the assembled total still misses the requirement.
| Integration testing | System testing | |
|---|---|---|
| Scope of assembly | A subset of modules, wired together | The whole product, fully assembled |
| Entry point | A module interface or API call | Where the user or client enters |
| What's real | Modules real, dependents often stubbed | Everything real, deployed together |
| Failure class caught | Contract breakage at one seam | Requirement-level behavior failures |
| Typical pipeline trigger | Every commit, fast and frequent | Pre-release, staging, or nightly |
| What a failure tells you | Two neighbors disagree on a contract | The assembly violates a requirement |
Two existing articles already draw the neighboring boundaries: integration testing vs E2E testing covers where integration ends and end-to-end begins, and unit vs integration vs E2E testing places all three against the pyramid. This one draws the boundary between system and integration instead.
How Autonoma covers both levels
System-level checks are expensive to write by hand, precisely because they need the whole assembly and a requirement to check it against. Someone has to know every dependency the flow touches, keep the environment wired together, and re-derive the requirement every time the flow changes shape. That's the kind of check that used to be a curated, hand-maintained suite when a human ran it: someone picked the critical flows and kept them current by hand. When checks are derived from the codebase and healed automatically instead, the gate stops being a suite anyone maintains and becomes a signal you regenerate.
Autonoma's agents read the codebase directly and run the checks they generate against the live, deployed application rather than a mock, which is what makes them system-level checks by construction: whatever they exercise is the whole assembled product, dependencies included. Because the checks come from what the product actually does instead of a hand-picked list, they hold up when a flow changes shape, and the Diffs Agent updates them as the code moves.
That's also where the requirement question surfaces. Whether a check verifies the flow against its spec, or validates that it's the right flow for the user (the distinction our verification vs validation piece works through), is a judgment an agent can't fully make. It can generate and run the check; someone still decides whether the requirement behind it is the right one.
Autonoma is not the integration layer, and it shouldn't try to be. Contract testing, service virtualization and mocking, and unit-level structural coverage belong to the frameworks built for exactly that job. We cover the layer above them: the system and end-to-end checks that only mean something once the whole product is running together.
Where the boundary actually moves
Microservices make this confusing because "module" stops meaning a function or a class and starts meaning an entire deployed service with its own database and its own API. That shift blurs the line on paper, but it doesn't move the boundary. It just changes what counts as a module. Martin Fowler has made a related point about integration tests themselves: many people assume they need to be broad in scope, when a narrower one often serves the seam better. A shallow check across those same services is what smoke testing for APIs and microservices covers.
The operational test still works the same way. Does this test assemble the whole product, its real datastore and dependencies included, and check it against a requirement? That's system testing, whether the "whole product" is a monolith or forty services behind a gateway. Or does it assemble a subset, maybe one service and one thing it calls, and check a seam? That's integration testing, whether the seam is a function call or an HTTP request.
The label on the test doesn't decide the level. How much of the real, assembled product has to be true does.
The tool doesn't decide the level either. The same test runner can write an integration test or a system test depending on what it's pointed at. A script that drives a single page against a mocked backend is an integration test. The same script, run against every real service the flow touches, is a system test. What changes is the scope of assembly under test, not the code that runs it.
That's the question worth asking before naming a test: not "does this use the UI," but how much of the real, assembled product this test actually needs to be true. The answer tells you which gate you're running, and for a release owner who wants that system-level answer without hand-assembling the check every time the flow changes, Autonoma is one way to get it without owning the wiring.
Frequently Asked Questions
The difference is scope of assembly. Integration testing wires together a subset of modules and checks that they honor their contract at the point where they hand data to each other. System testing assembles the whole product, real dependencies included, and checks it as a black box against the requirements. Because the scope differs, so does what each level catches: integration catches contract breakage at a seam, while system catches requirement-level failures that only appear once everything is wired together.
They overlap heavily but aren't identical. End-to-end testing usually means driving the product through its real UI the way a user would. System testing means testing the fully assembled product as a black box against its requirements, which is often done through the UI but doesn't have to be. In practice most system tests are E2E tests, but the defining property of a system test is the requirement it's checked against, not the entry point used to run it.
Usually, yes. Integration tests are cheap enough to run on every commit, so they act as a fast, early filter that catches contract breakage before anything more expensive runs. System testing needs the whole product deployed and running, so it typically runs later, in staging or a pre-release environment, after the integration suite has already passed.
Yes, and that's the whole reason the two levels exist. Every pairwise contract can hold exactly as written while the assembled product still violates a requirement that spans more components than any single integration test checks at once. A checkout flow where the tax service is handed the pre-discount subtotal is the classic case: both seams pass their contract tests, and the customer is still charged the wrong total.
Historically, a QA engineer or SDET who knows the requirements and can assemble the environment needed to check them: the real services, a seeded database, a working UI. That's also why system-level suites are expensive to maintain by hand, which is why generating them from the codebase itself, the approach Autonoma takes, is becoming the alternative.
No. Autonoma generates and runs system-level and end-to-end checks against the fully assembled, deployed product, which is the system layer, not the integration layer. Contract tests, service virtualization, and unit-level integration checks stay with the frameworks built for them; Autonoma covers the behavioral layer above them.




