Benjamin Cane PRO
Distinguished Engineer @ American Express building payments systems. Author: https://amzn.to/3kuCFpz, Thoughts, & Opinions are my own.
As ironic as it seems, the challenge of a tester is to test as little as possible. Test less, but test smarter.
— Federico Toledo
In short, Open Source projects have few resources available to them, driving testing to simple and low-level code. Enterprises often have many resources available, allowing complacent teams to spend too much time testing higher-level services ignoring or undervaluing the low-level testing.
There is often* an imbalance in testing in both Enterprise Software Development and Open Source Software Development.
* Note: This pattern may not apply everywhere, Especially within large companies where practices may differ from team to team
The premier tool for finding the right balance of software testing is the coveted "Test Pyramid."
Test Pyramid Takeaways:
The Test Pyramid is a great guide, but software development has changed since its creation.
When finding the right balance of testing, especially in a backend microservices context. I breakdown testing into four categories.
* Focused on a Backend Microservices world
This testing is often referred to as Unit testing. This level of testing is the lowest level possible. Focused on testing a single method/function and testing the return of those calls.
func TestNewCreation(t *testing.T) {
_, err := New(Config{})
if err == nil {
t.Errorf("New should have errored when Config is empty")
}
}
This type of testing is technically functional testing, but it leverages the same testing frameworks as unit testing. The goal is to test the basic functionality of the service while maintaining fine-grain control of the tests.
func TestAPI(t *testing.T) {
go func() {
server, err := app.Start(Config{...})
if err != nil {
// do stuff
}
}()
defer server.Shutdown()
t.Run("Verify GET on /", func(t *testing.T) {
r, err := http.Get("https://example.com")
if err != nil {
// do stuff
}
// verify response
})
}
Focused more on external functionality, this type of testing is similar to low-level functional testing. The major difference is that this type of testing can act as an external dependency and verify the tested service's actions.
Feature: Check Status
In order to know my service is up
As a caller of my microservice
I need to be able to call /ready
Scenario: /ready returns 200
Given Ready is true
When I call /ready
Then we should get a 200 ok
Focused on testing the functional interactions for an end-to-end platform. This type of testing is useful for verifying how the overall system behaves with various inputs and scenarios.
Feature: Run Transaction
In order to know my service is working
As a caller of my microservice
I need to be able to call /transaction
Scenario: /transaction returns 200
Given we call Transaction API
When I call /transaction
Then we should get a 200 ok
While not exactly a pyramid, these various types of testing have different levels of focus. The goal is to balance low-level testing and high-level testing. Not singularly relying on only one level of testing.
Testing Type | Amount of Testing | Comment |
---|---|---|
Low-Level "Unit" Testing | High | Highest value with least amount of effort, focus heavily on this level. |
Low-Level Functional Testing | High | Similar type of testing as Service Level, but with less complexity. |
Service-Level Functional Testing | Low | Reserve this testing for things that need a lot of external coordination/validation. |
Platform-Level Functional Testing | Medium | Test the end to end system, but don't neglect the previous levels. |
Software Engineering has changed over the last few years. The capabilities to enable testing have advanced, giving more flexibility and speed. But the expectations of Engineers have grown as well. Long gone are the days where it was acceptable to have software testing take several days to complete.
Today, Modern Software Testing should feel effortless and built into the software build pipeline. The below are characteristics expected from today's tests.
A practice that Open Source communities do well is ensuring that all software testing occurs as part of each pull request.
The goal should be to perform as much testing as possible before the code is merged.
For low-level testing, this is easy to accomplish; high-level testing is a bit more complex. For many, it means creating dynamic environments are part of the CI/CD process.
When doing Platform Level End to End testing, you must run the service being changed and any other services that make up the platform.
Dynamic Environments can be solved in many different ways, sometimes simple, sometimes complex:
Regardless of how Dynamic Environments are created, the most successful ones are those that Engineers can run on their local machine just as easy as their build pipeline.
With all of the different testing occurring as part of the build, it is important to be mindful of how long a build takes.
A faster build encourages smaller, more succinct changes. But with all of these different tests and validations running on a build, it is easy to let build times increase.
This doesn't mean we should test less, but rather we should optimize how we test. Focus on running multiple tests in parallel, optimizing the frameworks executing the tests, and focusing on the right levels of testing.
Many teams struggle to find the balance between low-level and high-level testing. Losing sight of the larger goal in the process.
"Does my change to this software work and not break things?"
When creating your Software Testing strategy/framework, focus on the goals:
Twitter: @madflojo
LinkedIn: Benjamin Cane
Blog: BenCane.com
Principal Engineer - American Express
By Benjamin Cane
Software Testing is critical to our ability to deliver fast, quality products. But many of us are doing a bad job at it. Either we have gaps in the name of speed, or we have a horrible testing experience. This presentation looks at how we can make it better.
Distinguished Engineer @ American Express building payments systems. Author: https://amzn.to/3kuCFpz, Thoughts, & Opinions are my own.