Unit Testing Best Practices: A checklist05 Jul 2021 #tutorial #csharp
In the last couple of months, we’ve covered the subject of Unit Testing quite a lot. 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 of the tips and best practices from my previous posts in one place.
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:
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
[TestMethod] to mark methods as tests. Other testing frameworks have similar attributes.
Don’t use filler words like “Success” or “IsCorrect” in test names. Instead, tell what “success” and “correct” means 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:
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:
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/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 value. 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
Prefer assertion methods for strings like
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 to 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. For example, don’t add flags to your stubs to return one value or another. Write separate fakes, instead.
Don’t write assertions for stubs. Assert on the output of your code under test or use 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,
Voilà! Those are my best practices to write
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 some of your code under test or change the assertions on purpose to see your tests failing.
If you want to have these best practices next to you, grab your free copy of my Unit Testing 101 ebook. Feel free to share it.
Remember, not because you don’t ship your tests to your end users, it doesn’t mean you shouldn’t care about the quality of them. Unit tests got your back when changing your code. They’re your safety net.