Testing Layers Of Back-End Services

Posted by

Every back-end application needs to have a proper testing strategy in place to ensure the service adheres to its quality goals. Those goals can include being defect-free, easily deployable, scalable, secure, and more, all without introducing breaking changes for existing clients.

The traditional test pyramid, as introduced by Mike Cohn, has three layers. The largest layer is reserved for unit tests, which most developers are very familiar with. The next layer up are the service tests, which, “…test the services of an application separately from its user interface.” And finally, the top of the pyramid is for the UI test layer.

The test pyramid that I like to use is based on Mike’s original but explodes the service layer into five separate layers in an effort to make the UI tests layer as small as possible.

Screen Shot 2017-09-05 at 8.17.06 PM

Unit Tests

The goal of unit testing is to take the smallest possible amount of code, usually a single function, mock out its dependencies, and verify that is behaves as expected.

How to write them?

There are many ways to write unit tests:

  • The Original Way. Write a method and then write the tests for that method.
  • The TDD Way: Write the test first, then write the code to make the test pass.
  • The BDD Way: Write your tests so that they test the behaviour of a method, not its implementation details.

An approach that combines the three methods above will ensure that your tests are written in a reusable way while covering most test cases but not going overboard with TDD.

When should they run?

Unit tests only need to run once in a CI/CD pipeline since their behaviour is predicatable and repeatable from one run to the next.

More Resources

If you’d like to know more about unit testing and TDD right now, I highly recommend Uncle Bob’s Agile Practices, Patterns and Practices book and his series on TDD at Clean Coders.

Acceptance Tests

Acceptance tests have two purposes that are achieved with the same suite of tests:

  • Verify that the requirements of the feature are satisfied. This can be called integration testing but the term acceptance testing fits better with Agile methodologies usage of Acceptance Criteria.
  • Ensure that the service is deployed correctly to a given environment. Without these tests you have no way of knowing if the code that was deployed is working as expected, making them especially critical when implementing a Continuous Delivery pipeline.

How to write them?

Let’s say you’re working on an API to add friends to a list of favorites. The acceptance criteria state that any existing user can be added to the list. Any request for an invalid user should be refused. For this fictional scenario, we’d need three acceptance tests:

  • When adding an existing user to the favorites list
    • returns an okay status code.
    • the user can be retrieved from the favourites list
  • When adding a non-existing user to the favourites return a bad request status code.

When should they run?

Acceptance tests should run every time the service is deployed to a new environment since they ensure that your service is up and running correctly.

Public Contract Tests

The purpose of Public Contract Tests is to ensure that developers don’t accidently introduce breaking changes to an API, whether it be via HTTP, a message queue or other.

How to write them?

These tests should be written once and never touched again. You can add tests to the suite but you should never remove or change the existing ones.

The tests should be checking the shape of the contract returned by every method in your service. Using the previous example, a successful request to retrieve the friends list would ensure that an array of users is returned, and that each element of the array has a properly formed user object.

When should they run?

Every time the service is deployed to a new environment.

Load & Performance Tests

Load tests measure how much traffic your service can handle before failing to respond. Performance tests measure the response time of your system at a given load level.

Generally speaking, a service doesn’t need load or performance tests until well into its life. They are mentioned here because any service that achieves a certain level of popularity will eventually need them.

How to write them?

There are tools and frameworks available that will do a lot of the heavy lifting for you (JMeter being a popular one). It’s then just a matter of integrating it to your Continuous Integration/Continuous Deployment (CI/CD) pipeline.

When should they run?

They can be run off-peak, on an environment configured identically to production, or on production itself if you’re feeling adventuresome.

External Dependency Contract Tests

Most services these days rely on external dependencies. Whether that dependency is inside or outside your organization, you should have tests that use that dependency in a similar fashion to your service. The goal is that you’re aware of any unexpected change that occurs to that depencency’s contract as early as possible.

How to write them?

These tests are very similar to writing Public Contract Tests since they have the same goal in mind but are for a different use.

When should they run?

Once for every build in your CI/CD pipeline.

Security Tests

Data breaches are happening on a daily basis these days, making these tests a requirement for any serious development. They are also among the hardest to write since they require lots of thinking outside the box.

How to write them?

There are a large number of tests that can be automated by invoking a deployed version of your service. Some low hanging fruit include, but are not limited to:

  • Validating that inputs are sanitized and rejected appropriately when invalid
  • Attempting to send large amounts of data as “data bombs” to an endpoint
  • Checking for valid, non self-signed certificates
  • Ensuring that all endpoints require authentication and enforce proper authorization.

There are great books available that address these issues in depth.

When should they run?

Because you can never know when or where an attack will come from, it’s best to run them on each deployed environment every time you deploy.

End to End Tests

Out of all the layers, this is the trickiest of the bunch. Because it integrates a front end with a back-end, they are notoriously hard to get right and are often more trouble than they’re worth. Luckily, with all the service-level testing we’ve done before getting to them, we should only need to test the happiest of paths.

How to write them?

There are many frameworks to automate UI steps, the most popular of which is Selenium as of the time of writing.

When should they run?

They should be part of every build, especially since these are the most brittle tests that you can write. As soon as one fails, you need to be ready to jump on it to fix it.

Putting It All Together

There are a lot of layers in the pyramid but it’s not necessary to have all of them from day one. Starting with Unit and Acceptance Tests will set a project up nicely for the future. Adding on additional layers as the need arises is a sensible approach to testing a modern, cloud-based back-end.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s