Data & Technology

Testing microservices - Why more tests aren't always better

March 27, 2026
Testing microservices - Why more tests aren't always better

Software testing is the foundation of high-quality software. Without a solid testing strategy, quality suffers—yet excessive testing can significantly prolong the development process. A good testing strategy must therefore strike a balance between necessary risk mitigation and efficiency. This is particularly challenging in distributed architectures like microservices and MACH (Microservices, API-first, Cloud-native, Headless).

Microservices change not only the system architecture but also the economic structure of the IT organization:
- Services become smaller
- Teams become more autonomous
- Deployments happen more frequently
- Interfaces multiply.

As a result, complexity increases not linearly, but exponentially.

While comprehensive system testing at the end of a release cycle was often sufficient for monolithic systems, it leads to massive inefficiencies in microservice landscapes: every additional service interaction increases potential failure paths.

The intuitive reaction of many organizations is:
- "Then we'll just test more"
- More integration tests
- More end-to-end tests
- More regression testing.

The result:
- Pipeline runtimes increase
- Analysis times increase
- Coordination costs increase
- Release frequency decreases.

The problem is not a lack of coverage, but a miscalibrated test distribution.

This is because test distribution and strategy influence many factors of product development: the longer the test runtimes, the longer the time-to-market and, consequently, the deployment frequency. However, this can reduce incident risk. Depending on how the tests are structured, coordination costs can either rise or fall.

In practice, however, we often encounter concepts that either pursue maximum risk mitigation with long lead times or provide insufficient coverage. This article shows how a test strategy can master this balancing act and be implemented in a practical way. To help with practical application, each chapter ends with a short checklist.

Unit Tests

Unit tests are the foundation of a stable testing concept. You should ensure that you are truly testing the smallest possible unit. This could be a method in the classic sense or, for example, an AWS Lambda function. It is important that this happens in an isolated environment and that the duration of an individual test remains well under one second.

Unit tests form the foundation and usually make up the largest share. If even one unit test takes significantly longer to run, the entire phase is delayed, which in turn delays the time it takes for a change to go into production.

The great advantage of unit tests is that they clearly show where an error lies. If a test fails, the error is located within the unit due to the isolation, eliminating the need for time-consuming analysis.

In my experience, good unit tests are the basis for successful projects: errors are detected early, and their causes can be narrowed down without much effort. However, this does not automatically mean that 100% test coverage is always useful. A rigidly pursued 100% quota, for example with tools like JaCoCo, can even lower test quality. In the end, the focus is often just on whether the 100% mark is reached, rather than whether all relevant paths are meaningfully covered. Especially with Java lambda expressions, JaCoCo currently does not reliably detect all paths and may incorrectly mark code as covered. Furthermore, this simple metric often displaces the important discussion about which code areas are critical. Everything is treated equally, and critical passages may then receive too little attention.

Unit Test Checklist:

  • Are only isolated units being tested?
  • Is the runtime of every single test under 1 second?
  • Are all paths in critical code areas being tested?

Integration Tests

Integration tests build on top of unit tests. As the name suggests, this is where the interaction between different components is tested. The components themselves can vary greatly: writing to a real database or reading and writing messages via a message broker.

This refers exclusively to integration tests within a microservice. Even within a single microservice, there can be multiple components where their interaction is essential for the microservice to function.

I have learned from my projects that integration tests are well-accepted by the team if they are stable, only fail when there is a real error, and do not exceed the build pipeline's time budget. To ensure this, the test environment must be stable and deterministic. It is also important to consciously decide which integrations should be tested. Depending on the size of the project, it is worth discussing this explicitly.

Integration Test Checklist:

  • Is the scope for the tests clearly defined?
  • Is the test environment stable and deterministic?
  • Is the time budget for the integration tests being met?

Contract Tests

In a microservice or MACH architecture, numerous components communicate with each other—either via classic REST interfaces and/or asynchronously via message brokers. The data exchanged follows a defined schema so that both sender and receiver can process the format. This definition is the "contract" between both sides. Any violation of this contract can lead to errors.

In my experience, contract violations are one of the most common causes of errors in distributed systems. They usually occur unintentionally or due to a lack of awareness that a specific field is important to a receiver.

Contract tests help avoid this. They check both the sender and receiver sides to ensure the agreed-upon contract is being upheld. Changes to the schema cause tests to fail specifically—this is not about functionality in the narrow sense, but exclusively about contract compliance. An example tool for contract testing is Pact.

Contract Testing Checklist:

  • Are contract tests in place?
  • Are contract tests implemented for both receivers and senders?

End-to-End Tests

End-to-end tests verify a system from start to finish without mocks, stubs, or similar aids. Testing is performed using real components. This means that when testing an API interface, not only the API component itself but also all other involved systems must be present—for example, the database, other services for authentication/authorization, etc. As a result, end-to-end tests can quickly become complex and resource-intensive.

While contract tests specifically check the contract, end-to-end tests focus on actual functionality in interaction; the data schema is secondary here.

In addition to interfaces, graphical user interfaces are often tested in an end-to-end context. Such tests can be implemented using tools like Playwright, Cypress, or the well-known Selenium. However, UI tests can quickly become very extensive: the tests must describe which buttons to click, which state changes to wait for, and so on. Anyone who has written such tests knows how complicated this can become.

The main reason for end-to-end tests is that they provide the greatest confidence in the overall system. These tests are often very important for non-technical stakeholders such as product owners and business analysts: end-to-end tests prove that a feature works in the interaction of all components and can be released without concern. All previous test types only show that sub-areas work, not whether the overall system is cohesive. As mentioned at the beginning, these tests are therefore popular with non-technical staff.

However, end-to-end tests have some fundamental disadvantages. For one, they are very fragile, especially UI tests: the smallest changes to the interface or network disruptions can lead to failures even when no real error exists. If such false positives accumulate, the pipeline is often simply restarted in practice. Only when tests fail repeatedly does one take a closer look.

Furthermore, UI tests take a comparatively long time. While earlier test stages are usually completed in milliseconds or seconds, a somewhat extensive end-to-end test can take minutes. If there are several of them, this pipeline step can quickly take 10 minutes or more. If the pipeline is then restarted due to unstable tests, the time-to-production increases significantly. Especially in distributed systems, you want to bring changes live quickly.

Another point: if an end-to-end test fails, the cause is rarely immediately apparent and must be analyzed, which can be very time-consuming depending on the reason. With unit tests, the cause is usually easier to isolate by comparison.

Nevertheless, end-to-end tests should not be dismissed entirely, as the aspect of confidence should not be underestimated. If these tests are successful, it is ensured that the application works. The previous test stages can only provide this level of confidence to a limited extent.

In my view, the aforementioned disadvantages lead to two recommendations for action:
- Define the functions that form the core of the platform together with stakeholders and write end-to-end tests only for these. This selection should be regularly reviewed, and tests should be removed if necessary.
- Work consistently toward stability: unstable tests do not create confidence, and their results are ignored. This only wastes resources and offers no added value.

End-to-End Testing Checklist:

  • Are only core functions being tested?
  • Is the time budget for end-to-end tests being met?
  • Are the end-to-end tests stable?

Manual testing

So far, we have only discussed automated tests. However, manual testing can also offer significant added value. It is often omitted because it is considered inefficient and there is a prevailing assumption that automated tests cover everything.

Manual tests are not part of the pipeline and can be performed independently of deployments. Both the test system and the production system can serve as a test environment.

Even with high automated test coverage, it can be useful to check an application manually, especially when it comes to new features. A basic understanding of the application is a prerequisite. Testers can then click through new features and deliberately try to provoke errors: canceling dialogs, changing window sizes, trying out edge cases, and so on. There are virtually no limits to the imagination. It is important that it is clearly regulated who is responsible for manually testing the application and that this process is integrated into the normal workday. This can be either a separate QA department or the developers themselves.

Manual testing is definitely time-consuming, and if errors are found, the root cause must be determined. Nevertheless, it should not be dispensed with: for one thing, it increases confidence in the application, and for another, hidden errors are often discovered earlier. Furthermore, you should not rely solely on user feedback: some user groups are very fault-tolerant and sometimes accept that certain functions do not work. A lot of time then passes before the error is noticed.

It will be exciting to see how AI can support or partially take over exploratory manual testing in the future. In my view, there is great potential here: AI agents could test applications in a targeted manner and relieve the burden on humans.

Manual testing checklist:

  • Is there a process for manual testing?
  • Are responsibilities for manual testing clearly defined?
  • Are the results reported back to the team in a structured way?

Cost of defect curve

Many teams talk about test coverage, test types, and test automation, but surprisingly rarely about the fact that a test strategy is essentially an economic allocation decision: where do we invest time and money in testing to minimize the total cost of defects?

This is exactly where the cost of defect curve comes in as a strategic guideline for test architecture. This is because the cost of defects does not rise linearly, but typically exponentially along their discovery stage. The same functional or technical error causes very different levels of effort depending on when the error is found.

The following table lists the individual test stages with their characteristics and how they influence testing costs.

Entdeckungsstufe Analyseaufwand Beteiligte Personen/Teams Kontext / Systemumfang Koordination / Abstimmung Zusätzliche Risiken / Kosten
Unit-Test Minuten 1 Person Isolierte Komponente/Funktion Keine Geringe direkte Kosten
Integrationstest Stunden 1–2 Personen Mehrere Komponenten Niedrig bis mittel Verzögerung im Test, ggf. Re-Planning
End-to-End-Test Potenziell Tage Mehrere Personen/Teams Gesamtsystem, systemübergreifende Flows Hoch Blockierte Releases, höhere Test-/Analyseaufwände
Produktion (Live-System) Stunden bis Wochen (gesamt) Mehrere Teams/Stakeholder Realer Betrieb, Kunden- und Marktumfeld Sehr hoch (Incident-, Management-Ebene) Reputationsschaden, SLAs, Incident-Management, ggf. regulatorische Risiken, Eskalationen

A strategically aligned test architecture systematically shifts defect detection to the front because the investment pays off at the early stages. Every defect detected early avoids a multiple of the costs that would arise if the same defect were only noticed during system testing or in production.

Nevertheless, this should not be understood as a dogma, but as a guide for setting priorities. Not everything should be shifted to the front as a matter of principle, but only where it makes sense. In certain situations, monitoring combined with rapid incident handling is sufficient instead of writing extensive tests.

Conclusion

Testing microservices and MACH architectures is not an end in itself, but rather the safety net for rapid release cycles. This involves not only technical decisions but, above all, organizational and project-specific ones: not everything needs to be tested, but the right things do, and that can vary from project to project. As shown, the key to success lies not in the sheer volume of tests, but in their targeted distribution:
- Unit tests provide a fast, stable foundation.
- Integration and contract tests secure communication in distributed systems without slowing down the pipeline.
- End-to-end tests create the necessary confidence for business stakeholders, but should be used sparingly and focused due to their complexity.
- Manual tests remain the corrective for the unpredictable: A coherent test concept balances quality and speed by avoiding redundancies and catching errors where they are cheapest to fix. Of course, functional verification is only one side of the coin. For a complete picture, aspects such as automation in the CI/CD pipeline, the setup of test environments, and non-functional tests (load and security tests) must also be considered. These are topics we may explore further in future articles.

Figure 1: Overview of the different test levels and the components tested in each

It is also helpful to define the following guardrails:
- Maximum pipeline duration:
A set duration for how long a pipeline is allowed to take from start to finish. If this time is exceeded, either build steps must be optimized or tests must be reduced.
- Minimum unit test coverage:
A defined minimum number of unit tests that must be present.
- Mandatory contract standards:
Clear rules and agreements between teams regarding compliance with data formats, as well as documentation of these formats.
- Zero tolerance for flaky tests:
Consistent deletion of flaky tests. Flaky tests only reduce confidence in the tests and, consequently, in the application itself.

It is important to understand the test concept as an economic allocation decision and to consider the cost-of-failure curve. This turns "building more tests" into a conscious, justifiable architectural decision and makes testing a central lever for the economic efficiency of your systems.

Use these checklists as a starting point to question your current strategy and optimize it step by step, or contact us to speak with our experts.

Alexander Piehl

Alexander Piehl

Senior Software Engineer