Shift-right: Test microservices in the wild to tame DevOps

If you're a test engineer, you've probably been hearing more about microservices over the past year or so. This isn't surprising, since many companies that have invested in DevOps are also moving to a more "microservices" way of creating software. This inevitably is bringing up quite a few challenges, especially in the area of testing—specifically regarding how to approach automated testing in a microservices world.

How to Build a DevOps Toolchain That Scales

Understanding microservices

Before we tackle how to start testing microservices, let's define microservice. There are two types of basic development styles. The more common one is called monolithic design. A great example of a monolithic application is the application that I'm using to write this article: Microsoft Word. It's called a monolith because it consists of a single application contained in one process.

Monolithic applications are "tightly coupled," or written with a group of classes that are highly dependent on one another. For example, if you have two classes (X and Y) and a change is made to X, that change will most likely have an impact on class Y. This is admittedly a simple example—most monolithic applications consist of a bunch of classes that depend on one another. If a change is made to this type of application, it usually follows that a new version of the whole application will need to be built and deployed.

By contrast, a microservices architecture consists of very small, extremely focused services that, when they come together, make up what is ultimately a complete application or task. Each instance of a microservice represents a single responsibility within your application.

What's really cool is that these services are independent of one another, which makes them independently deployable and testable.

Now it's time to look at some approaches for how to go about automated testing.

Testing in isolation

Microservices are tricky to test because you have lots of independent services communicating with other independent services in many (often unanticipated) ways. A good place to start your test automation efforts is to directly test the functionality of a specific microservice in isolation. Usually this is easily done by using a REST API to talk to your service and some sort of mocking to enable you to test the service alone, without any type of integration with your other services.

Testing in isolation is all fine and good, but what happens when you change and deploy service X? How will it affect service Y or A? How do you know that you haven't broken something? Not only should you make sure that your service itself is working, you also need to ensure that the other people who are consuming your service aren't going to be impacted by your changes.

End-to-end testing

Since we know that each microservice is independent and can be independently consumed in many ways, the concept of an "application" nearly becomes an illusion in this environment. Therefore, having a typical end-to-end test automation strategy isn't as effective as it is with other software architectures.

As a result, coming up with a traditional end-to-end testing approach that covers all possible workflows that a real user might perform isn't really going to work. You could use the 80/20 rule to identify the core journeys you think will be common, but you probably don't want to spend your efforts on trying to think of all the end-to-end possibilities at this level. A better approach would be to make a contract with your consumers.

Make a contract

It's nearly impossible for you to know all the ways consumers might use your services. With a consumer-driven contract model, it's the consumer's responsibility to provide a suite of tests that specify what types of interactions are needed and in which format. Your service would then agree to this contract and ensure that it's not broken. This gets rid of dependencies on other services. This approach also enables you to verify that the contract is being fulfilled at build time.

Tools like Pact will give you a better understanding of how you can achieve this type of functionality for developing and testing microservices. Once you have a consumer-driven contract process in place, the next key step in testing microservices is to shift-right into the previously forbidden world of production.

Shift-right into production

In a DevOps, microservices world, testing in production becomes an essential piece of your overall quality plan.

I recently interviewed Jeff Sussna on an episode of my TestTalks podcast. Jeff is the author of Designing Delivery: Rethinking IT in the Digital Service Economy. I really liked his analogy that as we build complex digital systems that are broken down into microservices and become more scalable, we are gradually shifting away from the world of so-called complicated systems as we start having relationships between systems of engagement and systems of record.

In my mind, complicated systems are similar to automobiles. They have lots of moving parts, but you can understand all the relationships. But with a microservices architecture, this complexity is more like flocks of birds, or economies, where you have these fluid, shifting relationships.

With the fluid, shifting relationships that a microservices-based architecture creates, how can we be certain about the ways our many services are going to be consumed? And how can we know in advance how our services might behave? How can we test this uncertainty? The answer is to start testing in production.

Monitoring and alerts

I realize that a lot of attention has been paid to the shift-left transformation many organizations are going through, but I also think that making a shift-right with proactive monitoring and alerting after you release your microservices into the wild is going to be just as important.

In a microservices world, you need to be able to react quickly at runtime to situations that can only occur in the wild and that you might not have anticipated. So having a key monitoring and alert system in place and having that tracing in production is critical. You'll want to know immediately if one of your services goes down or becomes unresponsive. By spotting an issue during production with the help of monitoring, you can often automatically roll back to the last known good version of the service before your users even know there's an issue.

Brave new world

Some of you may not feel comfortable in this DevOps world of ambiguity and uncertainty. But listening to your customer feedback and having an automated testing strategy in place, including testing your microservices in isolation, creating consumer contracts, and production monitoring and alerting, can help alleviate some of the anxiety you might be feeling.

For a deeper dive into microservices, I highly recommend you check out Martin Fowler's work on microservices, as well as Sam Newman's video on deploying and testing microservices.

Topics: DevOps