By design, Unit Tests are automated tests of small units of code that are tested in an isolated fashion. Essentially, an unit test is a program that calls the (
public) methods of a class and checks if the results are as expected
If you wrote your unit tests correctly, there are several benefits they will bring to your maintenance process:
- Unit Tests will find bugs while you're still developing: if you are refactoring a section of code, or even including a new one, and your past tests start to fail, it means that you changed the behaviour of that method and that might imply on a bug!
- Any correction cost regarding the newly found bugs is considerably lower than if they were found while in production
- In other words, Unit Tests protect your code against Regression: system changes that create bugs
- After changing your code, you must run the test suite! That way you'll find if there was any regression (if any test fails)
Just as we have our S.O.L.I.D. principles for programming in general, there are a couple of principles for general good practice in unit testing: the F.I.R.S.T. principles. Let's go over them.
The developer should not hesitate on running the test suite at any point of the development cycle, even if there are thousands of unit tests. They should run in a matter of seconds. If a test takes too long to run, it probably is doing more than it should — and, therefore, is not an unit test!
For any given unit test, it should be independent of everything else so that its results are not influenced by any other factor. With that definition, they should usually follow the “3 A’s of testing”: Arrange, Act, Assert (also known as “Given-When-Then”).
- Arrange: All needed data should be provided to the test when you are about to run, and it should not depend on your environment.
- Act: The actual method you are testing should be run.
- Assert: Unit Tests should only test one outcome, meaning each test should assert that one state of an object should be tested. Note that this does not mean that you should only check one variable of a class response, but that all variables tested should be related to the method you ran
Tests should be repeatable and deterministic: every single time you run a test their values cannot change, no matter the environment.
The test itself should tell you if it passed. There should not be any need of manually checking the values. Most assertion libraries (such as
Shouldly) work in favour of that principle.
Your test should cover all “happy paths” of a method (
xUnit makes that possible with
Theory tests), all edge cases (where you think the test might fail), illegal arguments, security flaws, large values… in sum, every possible use case scenario should be tested, not only enough to have (near) 100% Code Coverage
Following TDD, Unit Tests should be written before the production code that will be tested. This can be done by writing the abstraction (Interface) and then the test based on the method signature and spec alone. After the test is written, your code can be produced and tested.
.NET development, a few tools/frameworks are available by default, and a lot more available as NuGet packages. Here are some highly recommended options
- Base Framework:
xUnitis a powerful and complete testing framework for .NET that is usually bundled with VisualStudio alongside
NUnit. Overall both are good frameworks, but
xUnitis more readable and intuitive
- Assertion Tool:
Shouldlyis a much more intuitive way of asserting your test results than the native Assert class. The github repo gives plenty of examples but just one very simple sample should be enough to show how readable and simple
// using Assert Assert.IsTrue(booleanVariable); // using Shouldly booleanVariable.ShouldBeTrue();
- Mocking Tool:
NSubstituteis a powerful tool that allows you to create mocks for any given class or interface so that you can easily test any method that would, in a regular case, use external environment such as 3rd Party APIs or DataBases. It has a simple learning curve and is well documented
This article is a compilation made from different source materials cited below:
- General “Software Maintenance and Evolution” and “Software Testing” theory