Unit testing is a key part of having good code hygiene and most of us want to do it more often. In reality, however, writing unit tests is often put on the back burner when something more urgent (or interesting) crops up. It can also be daunting to build a test suite from scratch for a legacy codebase without one, and might be hard to figure out where to begin.
Whatever condition your code is in, there are a few ways you can get you started with writing unit tests for any type of application.
1. Start writing tests early
Catching up with unit test creation for a large existing codebase with minimal coverage is almost impossible to do manually. This is why, if you’re starting any new project, it’s important to shift left testing and plan to incorporate unit tests before you begin writing code. Writing tests before you write any code at all is called test-driven development (TDD). TDD keeps your workspace clean and helps you write lean code that functions exactly as intended. It also helps you write tests that are robust and accurate—tests that only fail when the code is really broken—so you avoid alert fatigue from too many false alarms and don’t miss a real failure.
Embracing TDD entirely can be time-intensive, but even using the principles of TDD as a starting point will lead to better quality code.
2. Write one test per case
A unit test is called a unit test for a reason: it tests one unit of your code. Don’t introduce too many variables in one unit test or you won’t be able to identify exactly what’s broken and might miss potential issues. The jury is out about whether or not to include just one assert per test, but doing so makes it more likely that the test can only fail for one reason, so any issues will be immediately obvious.
3. Think more about the intended behavior of the case you’re testing
Once you’re in the habit of writing frequent unit tests, you can improve those tests further by thinking about the intended behavior of the code you’re writing the test for, rather than just implementation. This is called behavior-driven development (BDD) and builds off of TDD practices. The idea is to test what the code should do, rather than the details of its implementation, because the functional behavior of the code is less likely to change and then require refactoring later.
4. Think less about arbitrary code coverage targets
Code coverage targets can be useful, but if you’re short on time (and you probably are) or working with a legacy codebase, try to instead write useful tests that target the most critical parts of your application or are most important for your organization. Test-driven development encourages having good unit tests for all of your code, but this doesn’t mean manually covering every possible permutation just for the sake of meeting a coverage target of, say, 80%. Try to find a middle ground between having no unit tests at all and spending too much time writing them.
5. Write your unit tests in a way that makes life easy for the person who will maintain them
Good unit tests should be easy to understand and tell you (or the person who gets to maintain your code) exactly what’s wrong at a glance. Picking a clear, descriptive name for your unit tests makes them instantly readable for your teammates and for you in the future. Similarly, write comments that explain your reasoning and add messages with assertions to make everything as straightforward as possible.
6. Maintain your tests like you maintain your production code
Following from the previous point: unit tests are code, too. A bad habit I’ve seen is to turn off tests when you’ve refactored your code and tell yourself you’ll fix the tests later. We all know later never comes! If you refactor your tests when you refactor your production code, unit tests stay evergreen and can also be one of the best forms of documentation.
7. Automate your testing
Automation can really help when you’re working with the accumulation of thousands of tests in your suite and integrating them across applications. Picking a good unit testing framework and using CI/CD tools can help you identify any issues as early as possible in the SDLC, when they're the easiest and least expensive to fix.
Those tips should help you get off to a good start, but sometimes legacy codebases have very few (or no) unit tests and catching up with new tests isn’t possible manually. Diffblue Cover can intelligently generate unit tests for both legacy and non-legacy code, so developers and organizations can see the full impact of the changes they make and produce better quality code, faster. Schedule a demo to see how!