Why You Should Canary Test In Production
- Contracts are King
- Fine, Let’s Program Everything
- Contract Driven Testing
- Trust No One
- Running Fast And Breaking Things
- Introducing: Canary
- A Bag of Opportunities
- Take Canary for a Spin
Testing, test strategies and tooling change often. Eight years ago we were all drinking the BDD coolaid, dreaming about our Product Managers writing our specs in Cucumber while we just happily write the code that makes them pass.
It took the testing world by a storm. But then it stopped.
The pendulum has swung, and today we’re seeing more of the simplistic style of testing taking the stage. Go’s testing package is one notable example of mainstream, simple, testing approaches. Some people even wanted a simpler test output.
Jest for example, is a mix. It takes a simplistic BDD-style testing approach; it takes BDD-the-language but not the bells and whistles of the frameworks, which leaves the extreme idealistic approaches out. It even has a minimalistic set of matchers, as opposed to the massive amount of matcher libraries that we used to see come out every now and then.
So, testing, test strategies, and frameworks all change as years go by.
Even their backing technologies change.
Contracts are King
Contracts don’t want to change often. SLAs ( Service Level Agreements) and nonfunctional requirements might never change, because you’ve got a lawyer to sign an actual courtroom-backed contract specifying your uptime, your support policy, and your service quality.
The result is that we have to test our contracts as if we’re our own clients — that means black box testing.
We can expect SLA definitions such as:
- %99.9 uptime (or any other specification in percent).
- All requests come below one (1) second for main flows, eight (8) seconds for secondary flows.
- Clean security postures on all APIs (i.e. don’t expose server headers).
- Early notification of breakage and maintenance, up to 72 hours ahead.
- And so on… (you get the point).
We can translate these requirements to tests:
- Periodical test for a main endpoints (see if up or down, such as the
/login
endpoint). - Benchmark public endpoint requests after deploys and periodically.
- Verify security standards on public endpoints.
- Test things that usually break — response contracts, error codes, SSL/TLS certificates.
What tools do we have? well, we can use BDD with Cucumber to test APIs, like in this article:
Scenario Outline: Check if user is able to submit GET API request
Given I want to execute service <serviceName>
When I submit the GET request as per the data in Excel Worksheet <EWorksheet>
And I validate status code
And I validate mandatory tag in response from Excel Worksheet <EWorksheet>
And I validate response content
And I validate tag values in response from Excel Worksheet <EWorksheet>
And I validate header parameter in response
This looks fancy. It’s declarative and it’s english. But if you read the rest of the article, before you know it, you’ll find out you have to fill in and implement an API testing framework yourself.
But even if you did, there’s arguments to be had about how maintainable this look like for an API testing solution. And, you have to carry cucumber around every time you want to do this, possibly in projects and teams that don’t care for, or don’t have cucumber support.
Fine, Let’s Program Everything
You might say, if we’re building everything from scratch, why not get rid of cucumber, and just code our own framework?
Here’s the idea using Jest:
it("makes a request", ()=>{
request("http://example.com")
.then(body=>expect(body).toMatchSnapshot())
})
This would have been a reasonable approach. But there’s a ton of boilerplate in it — if I’m expecting the response body to be in a certain shape, isn’t that all I’m supposed to specify?
Contract Driven Testing
Maybe what we’re really looking for is contract driven testing? A bunch of testing frameworks and contract specification formats:
All of these have tools to validate their own contract specifications, to varying levels of success. Tools like Dredd, and Swagger CLI.
But what about actual content? nope. There’s partial support. Also because for example, in Swagger (and the OpenAPI spec) examples are kind of secondary; they’re an extension. For good reason — Open API is a contract specification format; a contract says nothing about actual API response content values, it specifies types and constraints.
Trust No One
What about testing your dependencies? Say testing Twitter, if your product depends on that? How about some of your enterprise service partners?
You might not be able to get the specs for their contract, and they might not even have one. But you certainly know how what response and SLAs you’re expecting from them.
How do we test that with contract driven testing tools? (spoiler: we don’t).
Running Fast And Breaking Things
Some times you run fast and break things. But some things cannot break.
We usually take a bottom up approach — unit tests, integration tests, functional tests, and sanity tests.
But when running fast, we deploy a lot. The match between:
- Deploy workflow, orchestration, and tools (relatively static).
- Your software (changes a lot).
Is being exercised many times a day. When either change, and especially the deploy workflow changes, we don’t really have a mainstream way to test, or actually, meta-test all that. Does this even have a name?
That’s a top-to-bottom approach — black box testing. And for matching your workflow and software — that’s called Canary tests; and we need a tool to write and execute these quickly and easily.
Introducing: Canary
Canary is the Swiss army-knife of canary testing. It lets you test:
- Contracts
- Content, body, headers, status
- Certificates (SSL/TLS)
- Benchmarks and performance
- Uptime and SLAs
All in a generic, technology-independent way. Canary is written in Rust a modern systems programming language that is: fast, secure, and has an extremely low footprint.
Here’s your first Canary test:
http_interactions:
- request:
uri: https://google.com
response:
status_code: "200"
And here’s what happens when we execute it:
$ canary run
✔ verify: request - (https://google.com)
Ran 1 interactions in 246ms. Success: 1 Failure: 0 Skipped: 0
You can try this right now with nothing but Docker on your machine:
$ docker run -v "$(pwd)":/canary jondot/canary init
$ docker run -v "$(pwd)":/canary jondot/canary run
Quick. Simple. Fast.
A Bag of Opportunities
Canary is built to accommodate many use cases at once:
Your own project and repository flow.
Simply check in your .canary.yaml
file, and include Canary in your CI. Here it is on Travis CI:
# ...
script:
- yarn test
- yarn deploy:staging
- curl http://get.canaryops.io/install | sh
- canary run && yarn deploy:production
Canary will fail the build if needed.
As a centralized SLA validation tool.
If you have an engineering effectiveness or SRE team in your organization, they will see Canary as a great tool for validating healthy production standards.
A central repo with benchmarks, validations, and cert checks is something Canary can handle well.
Here’s how to generate various types of interactions:
$ canary init [with-vars|certs|benchmark|default]
As a homebrew global testing service.
Wrap Canary in a Lambda and have it execute from any point on the globe. Canary is built with Rust, and so startup time is extremely low, and its footprint is as compact as it gets.
You can also use Canary’s various reporters to ship back JSON results to your aggregated logs service, for analytics, aggregation, and forensics. You’re in control.
Take Canary for a Spin
Canary is optimized for a quick and lightweight experience. There’s nothing to learn but using your engineering instincts.
Check out the documentation and the features, as well as sign up to get a full license for free
For everything else, check out Canary’s official website: https://canaryops.io