Boy, testing in a serverless environment is tough, but before we dig into the technical details I want to do a quick review based on my experience and the experience of others (see the list at the bottom) on testing paradigm in serverless apps, why is it different, and why you need to change your thinking on testing. Let’s begin.
For those who are not familiar with, there is the famous testing triangle:
This is a famous triangle
The idea that stood behind this triangle is that you should concentrate the most at writing unit tests, less on integration tests, and even less on E2E/Manual, etc.
When moving more towards microservices there is a shift towards integration test, but why? The answer is because this time a lot of the interaction not only happens in code, but also in the configuration and the interfaces between the services.
Serverless is a microservice on steroids, but unlike microservices where your code is running in your microservices, you control the interfaces; serverless usually means outsourcing anything that is not related directly to your main business, from “basic” stuff like CI, source control to DB, compute engines up to machine learning models, data lakes, etc. The moment you do not control the other end, there is a danger that your code will not talk properly to the other side or the other side has decided to change its rules.
In the microservices world, and even in the traditional monolithic world, you write mocks that enable you to test everything locally with a high degree of certainty. Remember the fact that you own the code, which gives you a lot of power. But mocks are not good enough in the serverless world — not because there aren’t, serverless offline, AWS SAM, or localstack (and the list goes on) with official and less official mocks. The problem is twofold:
For me serverless testing means a different shape.
Unit tests are important — very important. Actually you should probably write them first, but in a serverless environment they are not the only important one because you need also tests that run in the cloud environment and tests that asses the quality of your cloud environment, which is something that only integration tests can give. Pay attention to the fact that you’ll probably have more unit tests than integration tests, by an order of magnitude, but you can’t skip integration tests.
But it does not end here: I believe that in order to produce good code as a developer in serverless environment you have to run and play with your code on an actual cloud environment, not as part of a CI, but part of the regular code → test
cycle. And here there is another paradigm shift: We, as developers were used to coding → test
locally on the computer, but I believe serverless will push us to code locally and test remotely.
I’ll be honest with you, I’m going to skip the unit testing part. I believe this area is well covered in other posts and in general it is no different than what you were doing up until now. Let’s move to the interesting part, which is integration.
In order to better understand our methodology, you need to know which services we used.
The fact that we are using two cloud providers complicates our development, but for us Firebase is a winner for mobile-related development and we preferred to work hard on integrating it.
As I wrote, serverless testing is a change in mindset, and each developer needs to have these prerequisites ready before writing a single line of code.
The ability for each developer to deploy the entire environment is something rather new; in the traditional world where you ran everything locally sometimes you couldn’t run everything (too complicated, not enough compute resources. Etc.), but here in the brave new world we embraced this capability.
It is not easy.
The above process although highly automated has couple of problems:
Now the fun begins. Let’s do a quick overview of the main flow we have in our integration tests
I’m cheating
This is not a client ! by Braydon Anderson on Unsplash
Adding a real mobile device to our integration test would make it too complicated, so instead we used a mocked device: We had code that was calling the same API the device was supposed to call. Testing a real device, which is sometimes called E2E testing (end to end), is done manually by the developer themselves before committing the code. No QA is involved. Actually we don’t have any QA, and developers are complete owners of the entire process (another part of the paradigm shift in serverless, but that’s another post).
We are using DRF to expose functionality that is not visible in production environment. This functionality is used for instrumentation purposes only, it is heavily monitored and disabled in production via configuration.
In order to verify that Firebase was acting properly we use its admin SDK to query the relevant services.
Writing tests and running them are totally different creatures in the serverless world. In your local development environment all you have to do is click play, but there is no play button for AWS :-(
That would be nice
So running our integration tests contains two phases:
pytest
.Now last but not least, we’ve actually created our own play button, hurray! Sometimes things break and they are found during the integration tests, and here unfortunately we fall short. The code → test
cycle I was talking about does not work very well in the cloud, and for us fixing a small piece of code usually means waiting between 30 to 60 seconds of cloud update.
This is a long and cumbersome process. It takes a lot of time to update, and there is a real gap here in tooling. I found an interesting solution called seed. I did not try it, but we use python + zappa, which is not supported.
You are more than welcome to share your experience with testing and development environment provisioning in serverless.