Unit Testing Best Practices: A checklist
05 Jul 2021 #tutorial #csharpAs part of this series on unit testing, we’ve covered a lot of subjects. From how to write your first unit tests to create test data with Builders to how to write better fakes. I hope I’ve helped you to start writing unit tests or write even better unit tests.
This time, I’m bringing some tips and best practices from my previous posts in one single place for quick reference.
1. On Naming
Choose a naming convention and stick to it.
Every test name should tell the scenario under test and the expected result. Don’t worry about long test names. But don’t name your tests: Test1
, Test2
, and so on.
Describe in your test names what you’re testing in a language easy to understand, even for non-programmers.
Don’t prefix your test names with “Test.” If you’re using a testing framework that doesn’t need keywords in your test names, don’t do that. With MSTest, there are attributes like [TestClass]
and [TestMethod]
to mark methods as tests. Other testing frameworks have similar ones.
Don’t use filler words like “Success” or “IsCorrect” in test names. Instead, tell what “success” and “correct” mean for that test. Is it a successful test because it doesn’t throw exceptions? Is it successful because it returns a value different from null? Make your test names easy to understand.
2. On Organization
Make your tests easy to find.
Put your unit tests in a test project named after the project they test. Use the suffix “Tests” or “UnitTests.” For example, if you have a library called MyLibrary
, name your test project: MyLibrary.UnitTests
.
Put your unit tests separated in files named after the unit of work or entry point of the code you’re testing. Use the suffix “Tests”. For a class MyClass
, name your test file: MyClassTests
.
3. On Assertions
Follow the Arrange/Act/Assert (AAA) principle.
Separate the body of your tests. Use line breaks to visually separate the three AAA parts in the body of your tests.
Don’t repeat the logic under test in your assertions. And, please, don’t copy the tested logic and paste it into private methods in your test files to use it in your assertions. Use known or pre-calculated values, instead.
Don’t make private methods public to test them. Test private methods when calling your code under test through its public methods.
Have a single Act and Assert parts in your tests. Don’t put test values inside a collection to loop through it and assert on each one. Use parameterized tests to test the same scenario with different test values.
Use the right assertion methods of your testing framework. For example, use Assert.IsNull(result);
instead of Assert.AreEqual(null, result);
.
Prefer assertion methods for strings like Contains()
, StartsWith()
and Matches()
instead of exactly comparing two strings.
4. On Test Data
Keep the amount of details at the right level
Give enough details to your readers, but not too many to make your tests noisy.
Use factory methods to reduce complex Arrange scenarios.
Make your scenario under test and test values extremely obvious. Don’t make developers decode your tests. Create constants for common test data and expected values.
Use object mothers to create input test values. Have a factory method or property holding a ready-to-use input object. Then, change what you need to match the scenario under test.
Prefer Builders to create complex object graphs. Object mothers are fine if you don’t have lots of variations of the object being constructed. If that’s the case, use the Builder pattern. Compose builders to create complex objects in your tests.
5. On Stubs and Mocks
Write dumb fakes
Use fakes when you depend on external systems you don’t control. Check your code makes the right calls with the right messages.
Avoid complex logic inside your fakes. Don’t add flags to your stubs to return one value or another. Write separate stubs instead.
Don’t write assertions for stubs. Assert on the output of your code under test or use mocks. Remember there’s a difference between stubs and mocks.
Keep one mock per test. Don’t use multiple mocks per test. Write separate tests instead.
Make tests set their own values for fakes. Avoid magic values inside your stubs.
Use descriptive names in your fakes. Name your stubs to indicate the value they return or the exception they throw. For example, ItemOutOfStockStockService
and FixedDateClock
.
Voilà! Those are my best practices for writing better great unit tests. Don’t forget to always start writing failing tests. And make sure they fail for the right reasons. If you don’t follow Test-Driven Development, comment out some of your code under test or change the assertions on purpose to see your tests failing.
We don’t ship our tests to end users. But it doesn’t mean we shouldn’t care about the quality of our tests. Unit tests got our back when changing our code. They’re our safety net.
Don’t miss the rest of my Unit Testing 101 series where I cover all these subjects in depth.
Happy testing!