This article is compiled and published by EAWorld.

Author: EAWorld

Translation: White little white

The full text is 4977 words and takes about 8 minutes to read

Summary:

In the world of software engineering, we often face change. Microservices have not only changed the architecture of software, but also the way teams are organized and collaborate.

Compared with monolithic applications, microservices have their advantages. At the same time, there are new problems after the introduction of microservices. Testing is one of the problems.

In this article, we want to give an overview of how testing is changing in the new world of microservices. We’ll also cover the details of consumer-driven contract testing and the framework that supports it.

In order to explain the concept of CDCT in a more comprehensive way, this paper has translated, quoted, and integrated the content of many related articles, and related links are attached.

Directory:

Unit testing

End to end (system) testing

Integration test

4. Using Consumer-driven Contract Testing (CDCT)

Five, the summary

Unit testing

Should we unit test when we talk about microservices? The answer is yes, unit testing has proven to be a reliable, fast, and inexpensive way to test the validity of business logic. But unit tests only guarantee that the code of either the service provider or the service consumer is valid or functional, not that the interaction between services is valid. This is one of the core application scenarios of microservices.

End to end (system) testing

When we talk about microservices, should we still do end-to-end testing? Yes, it’s important to do end-to-end testing, but when we talk about microservices, the complexity of deploying all the relevant calls from the service consumer to the service provider in order to perform the end-to-end testing can be very high.

Integration test

The traditional way to test the interaction between two services (provider and consumer) is to use integration tests. The goal is to run both consumer and provider services in some integration environment and check that they interact as expected. This type of testing simulates the behavior of services in a production environment, so integration testing makes sense in theory. However, there are some problems with this approach.

First, integration testing is usually slow. They need to set up the integration environment, start consumer and provider services, and initialize their dependencies. At first, this didn’t seem like a problem, but as the number of integration tests began to increase, the build process became slower and slower. This is especially true in microservices architectures. It is not appropriate to test integration between each pair of interacting microservices.

Another problem with integration tests is that they are fragile. Sometimes they fail for reasons unrelated to the service itself, perhaps with network problems or external dependencies such as databases. An integration test that means a failure does not necessarily mean there is a problem with the code.

Another problem with integration testing is localization difficulties. Even if the integration test fails due to an actual integration problem between the consumer and provider services, it is difficult to determine what the problem is: is this the fault of the consumer service? Or the provider’s service? Or is it both?

Integration testing adds additional team overhead. Integration testing is primarily performed by the QA team rather than by the developers themselves, which means that there is additional overhead between teams when problems occur. This also leads to the question of who is best suited to test integration points between two services: the QA team? Or the actual developer of the service? Having a clear idea of whether the two services are interacting correctly at development time can save us a lot of time and expense before we get to QA.

Use consumer-driven contract testing

(CDCT)

While each of the three approaches has its pros and cons, unit tests are relatively robust and reliable compared to integration testing and end-to-end testing, they work fast and tell us very specifically what the problem is. It would certainly improve our development, testing, and deployment experience if unit testing could be improved to validate service interactions in a more effective testing approach.

The idea behind Consumer-driven Contract Testing is to define a contract between each service Consumer and provider, and then independently test the Consumer and provider against that contract to verify that they comply with the contractual commitments.

To better understand, we will use the following sample model to describe the concepts behind this microservice testing approach.

In the figure above, we can see that two microservices communicate with each other through REST. The first service is the Consumer role, and the second is the Provider role.

When the service provider does not change, such as when we Mock the relevant feedback of the service provider, the relevant test can be passed.

However, in a production environment, the service feedback simulated during testing is likely to keep pace with changes in the service provider’s data format, such as from “name, name” to “name.” The integration tests will not be able to catch this problem because they are running against an outdated version of the provider, at which point the following happens.

The idea of consumer-driven contracts is to formalize the interaction between service consumers and providers. A service consumer creates a contract, which is an agreement between a service consumer and a provider about the interaction that will take place between them. Or put another way, present the expectations of the service consumer for the provider. Once a provider has agreed on a contract, both the consumer and provider can obtain a copy of the contract and use tests to verify that their corresponding implementation does not violate the contract.

Consumer-driven contract testing is typically implemented as follows:

1. Select the appropriate scenario and define the consumer’s request and expected response.

2. Use Mock mechanisms to provide consumers with Mock providers and expected responses.

3. Log requests sent by consumers, responses provided by providers, and other metadata about the scenario as contracts for the current scenario.

4. Mock the consumer, mock the request to the real provider.

5. Verify that the contract provided by the provider is the same as the previously recorded contract.

The advantage of this new testing approach is that they are basically unit tests with interactive conditions added: they can be run locally and independently, and they are fast and reliable. But this is comparable to the benefits of Mock simulation, and in fact, CDCT offers far fewer advantages.

Advantage 1: Reduced service consumer risk from interface changes

CDCT contracts are initiated by service consumers, who define the feedback information they need. Therefore, it is guaranteed that service consumers can always get the feedback they need. Regardless of what happens on the service provider side. Take PACT, the CDCT testing framework, as an example.

Service consumers can log requests, responses, and related information into a Pact file by creating mocks that simulate providers. This file is the contract between the consumer and provider. In this process, the service provider does nothing.

Next, on the service provider side, the Pact file is played back through a Mock that simulates the consumer, requiring the service provider to respond correctly to the contract. Through such a process, a complete drive from service consumers to service providers is completed.

When the service provider needs to make changes to the interface, it still needs to follow the requirements of the contract to feedback the correct results, so that the service consumer is guaranteed to always get the correct information regardless of how the service provider’s interface changes. Unless the consumer voluntarily recontracts.

Advantage 2: Decouple development teams, reduce testing costs, and free up productivity

When service consumers and service providers are decoupled through contract mediation, the technical teams involved are therefore decoupled without necessarily having to coordinate for an end-to-end test scenario.

Here we bring in two technical teams to conduct relevant tests. On the left is the service consumer, who needs to query the user’s email address by ID, and on the right is the service provider, who is responsible for feedback the correct email address information.

Establish a contract between the service consumer and provider, called TEST, to require the service provider to report the correct EMAIL based on the ID.

Service consumers can run through the TEST the TEST to get to know he will get the right information, but in fact, and it is not necessary, because only when the service provider side service interface changes occurs, will affect the effectiveness of the contract, so is the right thing, only need to be in the service provider of one party to the contract verification TEST.

In this way, the service consumer will drive the service provider to complete the specified functional feedback through the contract. When both parties coordinate the process and work properly, the service provider will no longer need the service consumer to issue any changes to the contract and can rely on the contract alone to discover code defects. The service consumer technology team, on the other hand, can focus on its own business and even support other project content.

Example: Third-party API integration test

In the real scene, on the one hand, can have many legacy systems API within the enterprise, on the other hand also at the same time, there will be a lot of things you need to call the external API, such as Google maps, these cases API not under our control, even submit feedback, related changes may also in weeks or months, or even for legacy systems, The suppliers involved no longer exist.

For scenarios where the application will integrate such apis, where the application is the consumer and the API is the service provider, there are three ways to handle this:

1. Consumer side manual checking: Make sure the application is still working by manually checking that it is doing what it is supposed to do and using the correct values from the API.

2. Real call on the server side: firstly, confirm that THE API is correctly integrated, and directly call the API to check whether the relevant functions are correct during the test, which will involve the impact of the test speed brought by the network and the consumption of the call cost. After all, every call is not free.

3. Record server-side feedback and play it back in the code base: In this case, you only need to call the API once and record the feedback as a JSON file, which solves the network and cost issues, but still doesn’t get around the impact of any changes to the service interface.

The introduction of CDCT could alleviate this problem. But obviously we can’t publish the contract to the Google Maps API or our legacy CRM system and force them to comply. These providers may neither care about nor have the tools to support CDCT. So, at first glance, using CDCT for third-party apis seems strange.

What we can do is create another service as an alternative to Google’s API during automated testing. The service will hold the contract that defines the required fields from the actual API. We call these services agents. They never broker HTTP requests, but rather act as an intermediary between Google apis and applications during automated testing. The agent will have two goals:

1. Make sure the API responds as expected, as if you were actually calling a real Google API.

2. Provide contract files to service consumers for playback, similar to a JSON response file.

Let’s take an example, we’re going to show how long it takes to get from Stuttgart, Germany, to Berlin. Using the Google Distance Matrix API we make the following call:

http https://maps.googleapis.com/maps/api/distancematrix/json \    origins==Berlin destinations==StuttgartCopy the code

The result of the call is

{      "destination_addresses" : [ "Berlin, Germany" ],      "origin_addresses" : [ "Stuttgart, Germany" ],      "rows" : [          {                "elements" : [                    {                        "distance" : {                            "text" : "636 km",                            "value" : 635736                        },                        "duration" : {                            "text" : "6 hours 18 mins",                            "value" : 22651                        },                        "status" : "OK"                    }               ]          }       },      "status" : "OK"   }                                                        Copy the code

(Slide left and right for a complete view)

Through such a request call, we learned that driving from Berlin to Stuttgart takes about 6 hours and 18 minutes. This time is converted by 22651. This is the duration in seconds. Consumers of our services, such as Android applications, may want to decide how they want to format this value for the user. So we should make sure that the line time field is included in the response, that is, we have a contractual agreement for this value.

Taking the Groovy DSL of the Spring Cloud Contract as an example, we can define the following Contract:

org.springframework.cloud.contract.spec.Contract.make {    request {        method GET()        url("/maps/api/distancematrix/json") {            queryParameters {                parameter 'origins': 'Berlin'                parameter 'destinations': 'Stuttgart'            }        }    }    response {        status 200        body([            rows  : [[                         elements: [[                                        duration: [                                            value: 22651                                        ]                                    ]]                     ]],        ])    }}Copy the code

As you can see, the contract contains only the partial response we care about and the request that should be made to create the expected response, as opposed to the previous full feedback. The framework will automatically generate the following test code and assertions for related fields.

@Testpublic void validate_shouldProvideDistanceBetweenTwoCities() { // when: Response response = webTarget .path("/maps/api/distancematrix/json") .queryParam("origins", "Berlin") .queryParam("destinations", "Stuttgart") .request() .method("GET"); String responseAsString = response.readEntity(String.class); // then: assertThat(response.getStatus()).isEqualTo(200); // and: DocumentContext parsedJson = JsonPath.parse(responseAsString); assertThatJson(parsedJson).array("['rows']") .array("['elements']").field("['duration']") .field("['value']").isEqualTo(22651); }Copy the code

(Slide left and right for a complete view)

If we provide the specified parameters in the request (the when part), we should expect the specified response (the then part). The generated contract tests can pass without us writing any implementation code.

And after the test runs, we get JSON files as stubs, similar to PACT contract files, stored locally to apply the test.

If the actual Google API service changed the travel time from 25561 to 25562, the above code might not be applicable. We need to modify the generated assertion to something like this:

 assertThatJson(parsedJson).array("['rows']")    .array("['elements']").field("['duration']")    .field("['value']").matches("\\d+");Copy the code

This way, the service consumer won’t crash during the actual call, even if the Google API returns 12345.

In addition, to make the test hit the stub rather than the real API, we need to configure the following service mapping.

stubrunner:    ids: 'co.hodler:scdcproxy:+:stubs'    stubsMode: LOCAL    ids-to-service-ids:        scdcproxy: google-distance-serviceCopy the code

By using CDCT technology, we made sure that

  • The API behaves as expected.

  • Except for the proxy project, our tests don’t call the real API.

  • We make sure there is no mismatch between the expected response and the actual response.

Introduction to mainstream Frameworks

Frameworks that can accomplish CDCT tasks include Janus Pact Pacto Spring Cloud Contract. Pact and Spring Cloud Contract are widely available online.

PACT

(https://docs.pact.io/)

The description on its website reads:

PACT is a contract testing tool. Contract testing is a way to ensure that services, such as API providers and clients, can communicate with each other. Without contract testing, the only way to know that services can communicate is to use expensive and fragile integration tests. Did you set fire to your house to test your smoke alarms? No, you use the test button to test the contract between it and your ear. PACT provides test buttons for your code, allowing you to safely confirm that your applications will work together without having to deploy the world first.

Pact is an open source framework originally created by developers and consultants at REA Group, Australia’s largest real estate information provider. REA Group’s development team has been using microservices architecture in their projects for a long time, and there is a consensus among the team on the importance of agility and testing, so it is natural to design such an excellent framework and apply it to their daily work.

Tool Pact in 2013 began to open source, developed to today has already formed a small ecosystem, including various language (Ruby/Java/.NET/JavaScript/Go/Scala/Groovy…). Pact implementation, contract file sharing tool Pact Broker, etc. Pact is used by several well-known companies, including RedHat, IBM, Accenture and others, and Pact has become the de facto industry standard for contract testing.

Spring Cloud Contract

(https://cloud.spring.io/spring-cloud-contract/)

The Spring Cloud Contract is a complete solution to help users successfully implement a consumer-driven Contract approach. Currently, the main body of the Spring Cloud Contract is the Spring Cloud Contract Verifier project.

Spring Cloud Contract Verifier is a tool that supports consumer driven Contract (CDC) development for JVM-based applications. Write a Contract Definition language (DSL) in Groovy or YAML.

Spring Cloud Contract Verifier elevates TDD to the level of software architecture.

Five, the summary

The key concept of consumer-driven contract testing is twofold:

First, there is decoupling between service consumers and service providers by providing mediation contracts

Secondly, the contract should be issued by consumers to ensure that the value of service consumers can be realized first

The resulting benefits are:

First, the change of the interface of the service provider has no impact on the service consumer

Second, it reduces the expensive cost of traditional integration testing and end-to-end testing.

The third is fast feedback, independent deployment, reduced complexity, faster development and shorter iteration time.

This article directly quotes or refers to the following article sources:

1.https://blog.csdn.net/wzxq123/article/details/80219772

2.https://techbeacon.com/end-end-vs-contract-based-testing-how-choose

3.https://techblog.poppulo.com/why-should-you-use-consumer-driven-contracts-for-microservices-integration-tests/

4.https://dzone.com/articles/consumer-driven-contracts-with-pact-feign-and-spri

5.http://www.lor.beer/a-guide-to-testing-microservices/

6.http://www.lor.beer/how-to-consumer-driven-contract-tests/

7.http://hecodes.com/2016/10/better-testing-microservices-using-consumer-driven-contracts-node-js/

8.https://blog.novatec-gmbh.de/introduction-microservices-testing-consumer-driven-contract-testing-pact/

9.https://medium.com/@axelhodler/integration-tests-for-third-party-apis-dab67c52e352

10.https://www.cnblogs.com/Wolfmanlq/p/7966408.html

About EAWorld: Microservices, DevOps, data governance, mobile architecture original technology sharing,Long press the QR code to follow

Lesson notice! November 2nd (Friday) 14:30 PM Puyuan project manager Li Jun to share “bank micro service architecture practice”, in this public number reply “YG+ micro signal” immediately enter the group and complete the registration!