One of the most important tasks when testing software is to find a minimum environment for each specified test case in which this test can be executed in a meaningful way. The classes or objects of a software represent the building blocks from which the functionality of the application is composed. By a sufficient measure of unit tests one can ensure that the individual components behave as expected. With system tests, you look at the entire application as a single unit, without looking at the individual building blocks that make up the system and how they interact.
Integration tests bridge the gap between these two testing concepts by focusing on the interfaces between components and ensuring that their interaction works as expected. In practice, they are perhaps the most important building block in the testing concept, because system tests are too large to provide a sufficiently accurate indication of the source of an error. Unit tests, on the other hand, are too small to make a usable statement about the behavior of the entire application.
In addition, system tests require a complex test environment and many system resources to execute. This makes them too slow and too expensive to serve as the foundation of a testing strategy. We know of companies where all system tests took several hours to execute. For timely feedback especially on those bugs that have just been introduced as part of maintenance or further development, tests in a smaller scope are needed. Only these help the developers to immediately assign errors that have occurred to the changes that have recently been made. If it takes too long for the feedback to reach the developer, testing in the sense of agile software development is no longer possible.
Integration tests ensure that several code units, whose correct behavior in isolation from each other has already been ensured by unit tests, work together as expected. Testing from one system or component boundary to another such boundary is referred to as edge-to-edge testing. Building on the results of the integration tests, end-to-end system tests in the form of acceptance tests ensure that the system behaves as expected by the client.
In Behavior-Driven Development (BDD), which has received some attention in recent years, acceptance tests are specified in what is known as the ubiquitous language. This is a common language used by all parties involved in the project. In BDD, the individual components of the acceptance criteria are mapped by a parser to method calls in a test class. In contrast to test-driven development, which focuses on the state of objects, Behaviour Driven Development focuses on the behavior of the application.
However, there is no technical reason to perform acceptance tests exclusively as end-to-end tests through the user interface. Many business rules, such as plausibility checks, can be tested in a much smaller test environment without much difficulty. Through sensible organization and meaningful names of test classes and methods, even classic unit tests can be sufficiently readable and understandable for non-technical personnel, for example by using Testdox to generate documentation directly from the test code.
If the architecture of the software allows small unit tests as well as medium-sized edge-to-edge tests to be realized without great effort, then nothing stands in the way of using Experiment-Driven Development or Testing in Production. In these new approaches, which are based on ideas by Eric Ries among others, a new feature is first implemented as an experiment, which - possibly only for a subset of the users of a web application - is tested in production for its business value. A feature may even be implemented to meet quality objectives such as performance, scalability, or maintainability only after it has proven its business value. The decision is made using statistical methods on data and metrics collected in production.
The prospect alone of being able to further develop both the business model and the software in an agile and experiment-driven manner should be reason enough for many companies to rely on a modern, strongly decoupled application architecture. With a little technical infrastructure that provides the necessary flexibility for creating and wiring the individual objects, experiments in live operation are possible with almost no additional effort.