Unit testing has been around since the dawn of testing. Indeed, unit tests are so common and basic that they sometimes feel like the most boring type of tests. But the fact is that unit tests are the building blocks of the rest of your software delivery and testing pipeline. Even though unit tests may seem so simple that they're not worth thinking much about, it's critical to make sure you are doing unit tests right, so that the rest of your testing strategy has a solid foundation.
Toward that end, let's take a look at unit testing best practices.
To put it bluntly, a unit test is a piece of code that tests the functionality of another piece of code. Unit tests examine the most fundamental unit of any application. Only by getting unit testing right can we move on to talking about other types of testing like integration testing, performance testing, and UI testing.
The DevOps methodology advocates a “shift left” approach to testing. This means that tests are performed earlier in the lifecycle of an application. Unit tests are the earliest tests conducted, and they need to be run alongside development. It's not enough for developers to write code and throw it over the wall to QA for testing. Developers are now required to own the quality of the code they write. Smaller teams that are structured according to an app's microservices facilitate this kind of ownership. QA's involvement starts at the planning and design stage, but important developers have become champions for quality, and that is the biggest change that DevOps brings.
Unit testing should also be done more frequently. Unit tests should be run soon after code is written. But for this to happen, unit tests should be easy to create and should be able to deliver feedback almost immediately. (We'll discuss how to implement this later in the post.) The goal is not to achieve 100% code coverage, but to thoroughly test the most important functionality of the application using any number of unit tests as required.
As unit tests are run earlier and more frequently, they build a level of confidence in the quality of code being shipped.
This may seem obvious, but often, tests that start out as unit tests end up as integration tests, complete with logical statements and touching various parts of the application. This makes it difficult to isolate issues that come up, requires a lot of resources to run, and greatly slows down the pace of software delivery. Unit tests should take minutes to write, seconds to run in parallel alongside other unit tests, and be flexible enough to adapt to the changing state of the system. As unit tests become more focused and more lightweight, they become an enabler of modern methodologies like GitOps.
GitOps is an extension of DevOps with a focus on source code repositories and an end-to-end pipeline for greater automation. As source code repositories become the important starting point for the development process, checks and balances need to be in place to ensure good code makes it through to the master repository (and all the way to production), and bad code is flagged even before a commit. This leaves a trace of a clean commit history. Unit testing is the gate that code must pass through before every commit.
In a post I wrote for TheNewStack, I discussed these kinds of unit tests being a “dry run” for developers to get real-time feedback on the quality of the code they write. Implementing this kind of real-time dry run requires very efficient test infrastructure. It’s not been a realistic option until now for that reason — it’s hard to implement. However, recent developments have made this kind of pre-commit unit testing possible. That's what we’ll talk about next.
Headless testing (or browserless testing) has been around for a while, but it was previously used only to take screenshots of websites. Without having to load a web page on the client side, headless testing makes unit tests much faster. By taking advantage of lightweight container instances in the cloud, headless testing enables developers to write tests quickly and get feedback in near-real time.
Whether it's tried and tested principles like keeping unit tests small, or newer ones that enable GitOps, unit testing plays a key role in modern application delivery. With the advancement in cloud infrastructure, new developments like headless testing have the potential to completely change the way testing is done. By adopting headless testing and the best practices for unit testing as outlined here, DevOps teams can enjoy a faster pace of deployment and highly usable applications. This is the original promise of DevOps, and headless unit testing is just one more step in the right direction.
Learn more about which frameworks work best in which unit testing and how to choose the best one for their specific C# code in our article comparing NUnit, xUnit, and MSTest frameworks.
Twain Taylor is a Fixate IO Contributor and began his career at Google, where, among other things, he was involved in technical support for the AdWords team. His work involved reviewing stack traces, and resolving issues affecting both customers and the Support team, and handling escalations. Later, he built branded social media applications, and automation scripts to help startups better manage their marketing operations. Today, as a technology journalist he helps IT magazines, and startups change the way teams build and ship applications.