DevOps teams and developers have introduced several approaches to software testing. In this article, you will learn about two fundamental types of software testing, unit testing and integration testing, and how your team can implement them in your CI/CD pipelines to validate your code quickly and deliver new features to your users with confidence.

Unit testing and integration testing are important parts of a testing strategy called the testing pyramid.

The testing pyramid shows complementary test categories

The diagram explains the concept, but in practice it isn’t always obvious which are unit tests, integration tests, or other types of testing. Test categories are complementary, not exclusive. Ideally, your team will find the best place to use unit testing and integration testing in your pipelines. Before you can do that, though, you need to understand the differences between these types of testing.

What is unit testing?

Unit tests focus on one part of an application in total isolation. Usually, that means a single class or function. The tested component should be free of side effects so it is easy to isolate and test. Without this level of isolation, testing can become more challenging.

Other factors can limit the usefulness of unit testing as well. For example, in programming languages with access modifiers such as private or public, you can’t test the private functions. Sometimes there are special compiler instructions or flags to help get around these restrictions. Otherwise, you need to apply code changes to make these restricted helpers accessible for unit testing.

Execution speed is one of the key benefits of unit testing. These tests should be free from side effects, so you can run them directly without involving any other system. This should include no dependencies on the underlying operating system, such as file system access or network capabilities.

In practice, some dependencies may exist. Other dependencies can be swapped out to allow for testing in isolation. This process is called mocking.

Unit testing is also the heart of an advanced software development process called test-driven development. In the test-driven dev process, DevOps professionals and developers write tests before implementation. The goal is to have the specification of a single unit roll out before its realization.

While the enforcement aspect of such a contract can be appealing, there are notable downsides. The specification must be exact, and the test writers should know at least part of the implementation from a conceptual point of view. This requirement contradicts some Agile principles.

Now that we have explored unit testing in detail, we can learn how integration testing differs.

What is integration testing?

We have learned that, in practice, the isolation property of unit tests may not be enough for some functions. In this case, one solution is to test how parts of the application work together as a whole. This approach is called integration testing.

Unlike unit testing, integration testing considers side effects from the beginning. These side effects may even be desirable. For example, an integration test could use the connection to a database (a dependency in unit testing) to query and mutate the database as it usually would.

You would need to prepare the database and read it out afterward correctly. DevOps often “mocks away” these external resources the way mocking is used in unit tests. This results in obscuring the failures caused by APIs beyond their control.

Integration testing helps find issues that are not obvious by examining the implementation of an entire application or one specific unit, which helps discover defects in the interplay of several application parts. Sometimes, these defects can be challenging to track or reproduce.

While the lines between the various test categories are blurry, the key property of an integration test is that it deals with multiple parts of your application. While unit tests always take results from a single unit, such as a function call, integration tests may aggregate results from various parts and sources.

In an integration test, there is no need to mock away parts of the application. You can replace external systems, but the application works in an integrated way. This approach can be useful for verification in a CI/CD pipeline.

Unit testing and integration testing in CI/CD

Tests need to run to be effective. One of the great advantages of automated tests is that they can run unattended. Automating tests in CI/CD pipelines is a best practice according to most DevOps principles.

There are multiple stages when the system can and should trigger tests. First, tests should run when someone pushes code to one of the main branches. This situation may be part of a pull request. In any case, you need to protect the actual merging of code into main branches to make sure that all tests pass before code is merged.

Set up continuous delivery (CD) tooling so code changes deploy only when all tests have passed. This setup can apply to any environment or just to the production environment. This failsafe is crucial to avoid shipping quick fixes for issues without properly checking for side effects. While the additional check may slow you down a bit, it is usually worth the extra time.

Occasionally, you may also want to run tests against resources in production, or some other environment. This practice lets you know that everything is still up and running. Service monitoring is even more important to guard your production environment against unwanted disruptions.

Because your CI/CD pipelines should be fast, it makes sense to have most of the tests running as quickly as possible. Often, the fastest option is to use multiple unit tests, but the overall key metrics are coverage and relevance.

Development teams must create an efficient, reliable test setup for their projects, one that covers all relevant code paths. Make automatically running these tests in your CI/CD pipeline a high priority for your team. A combination of testing methods enhances test coverage and makes your software as bug-free as it can be.

Conclusion

Unit testing and integration testing are both important parts of successful software development. Although they serve different yet related purposes, one cannot replace the other. They complement each other nicely.

While writing unit tests is often faster, the reliability of integration tests tends to build more confidence for key stakeholders. Use both test strategies to ensure that your application is working today and continues to work tomorrow.

Use your CI/CD tools to run your team’s tests automatically, triggered when something changes, at regular intervals, or on demand. More tests create more data, and more ways to make sure your team’s software applications remain stable in production. To see how an automated testing strategy can increase your team’s development velocity and eliminate costly and inefficient manual processes, sign up for a free CircleCI account today.