By Anthony ValloneIf you haven’t noticed, Google has been launching many public APIs recently. These APIs are empowering mobile app, desktop app, web service, and website developers by providing easy access to Google tools, services, and data. In the past couple of years, we have invested heavily in building a new API infrastructure for our APIs. Before this infrastructure, our teams had numerous technical challenges to solve when releasing an API: scalability, authorization, quota, caching, billing, client libraries, translation from external REST requests to internal RPCs, etc. The new infrastructure generically solves these problems and allows our teams to focus on their service. Automating testing of these new APIs turned out to be quite a large problem. Our solution to this problem is somewhat unique within Google, and we hope you find it interesting.

System Under Test (SUT)Let’s start with a simplified view of the SUT design:

A developer’s application uses a Google-supplied API Client Library to call Google API methods. The library connects to the API Infrastructure service and sends the request. Part of the request defines the particular API and version being used by the client. This service is knowledgeable of all Google APIs, because they are defined by API Configuration files. These files are created by each API providing team. Configuration files declare API versions, methods, method parameters, and other API settings. Given an API request and information about the API, the API Infrastructure Service can translate the request to Google’s internal RPC format and pass it to the correct API Provider Service. This service then satisfies the request and passes the response back to the developer’s app via the API Infrastructure Service and API Client Library.Now, the Fun PartAs of this writing, we have released 10 language-specific client libraries and 35 public APIs built on this infrastructure. Also, each of the libraries need to work on multiple platforms. Our test space has three dimensions: API (35), language (10), and platform (varies by lib). How are we going to test all the libraries on all the platforms against all the APIs when we only have two engineers on the team dedicated to test automation?Step 1: Create a Comprehensive APIEach API uses different features of the infrastructure, and we want to ensure that every feature works. Rather than use the APIs to test our infrastructure, we create a Test API that uses every feature. In some cases where API configuration options are mutually exclusive, we have to create API versions that are feature-specific. Of course, each API team still needs to do basic integration testing with the infrastructure, but they can assume that the infrastructure features that their API depends on are well tested by the infrastructure team.Step 2: Client Abstraction Layer in the TestsWe want to avoid creating library-specific tests, because this would lead to mass duplication of test logic. The obvious solution is to create a test library to be used by all tests as an abstraction layer hiding the various libraries and platforms. This allows us to define tests that don’t care about library or platform.Step 3: Adapter ServersWhen a test library makes an API call, it should be able to use any language and platform. We can solve this by setting up servers on each of our target platforms. For each target language, create a language-specific server. These servers receive requests from test clients. The servers need only translate test client requests into actual library calls and return the response to the caller. The code for these servers is quite simple to create and maintain.Step 4: IterateNow, we have all the pieces in place. When we run our tests, they are configured to run over all supported languages and platforms against the test API:

Test Nirvana AchievedWe have a suite of straightforward tests that focus on infrastructure features. When the tests run, they are quick, reliable, and test all of our supported features, platforms, and libraries. When a feature is added to the API infrastructure, we only need to create one new test, update each adapter server to handle a new call type, and add the feature to the Test API.

11 comments
:

To be clear, except for the "API Infrastructure Service", every piece in the final diagram is part of the testing framework being described here?That seems like an impressive amount of frameworking, but you are solving, rather elegantly, a fairly complicated problem set.In terms of the framework itself, would you be able to estimate how the work breaks down to build and maintain it? Is it 50% Test API, 25% Test Library, etc?Did you have to build the whole thing before you started writing tests, or were you able to write some tests with only some pieces in place, and then iterate towards completion, evolving the tests along the way?Sorry to badger you, but given the generally vast resources, both in machines and people, available at Google, I'm very curious about the process through which something like this would evolve.Thanks!

Hi Alec, I just updated the final diagram with improved color coding and labels. The boundaries of the SUT, test case, and test infrastructure components should be more clear now. The amount of work was approximately: 80% Test API, 15% Abstraction Library, and 5% Adapter Server. We were able to iterate the development - always a good thing. The first iteration had a few basic API features working, an adapter for one language and one platform, the client abstraction library, and a single test. This became the proof of concept. We were happy with the initial results, so we decided to proceed with the design.

Hi Li, The tests are organized in code with clear comments. At Google, we have an internal service that stores, queries, and provides a UI for looking over the results of all tests, including the log output.

Do you simulate the APIs, for purposes of either functional or performance testing? In other words, are you able to make requests from the client library to a simulator, without accessing the live system? I'm curious to learn how you do that, if you do. Thanks.

Thanks Anthony. We're looking to simulate APIs in order to test, for example, client code before the real API is ready. The simulator would examine the request and send an appropriate reply back to the client. This provides functional testing. We could also do performance testing on the client side by firing off huge numbers of requests, again without affecting the real server. It gets complicated when you consider the fact that the requests can be HTTP, REST, EJB, etc, and there are multiple ways of creating the simulators themselves (request/response, WSDL, ...). There are a number of vendor products that will do this, in a variety of ways, but I'm interested in learning how large corporations perform simulations. Google is about the best example I could think of, given their size and client API library. Do you know where I can get more information on these best practices? Thank you.

It really depends on the focus of your testing. Just client functional testing: fake the server. Full system functional/performance testing: real server. There is rarely good reason to load test a client in a client-server system, as clients represent a single node instance of the system.

Anthony, Blog topic seems hot to me. I am surprise that there is so little discussion activities? I believe that its got to be some knowledge available to leverage. Our company has around 20+ services in Amazon cloud. They do interact and do depend on each other. Deployment, upgrade, etc became very difficult to execute. What You suggest to be done in testing environment? To develop Testing infrastructure like WTT (Microsoft) is not practical. It will take years to implement. Please advice.