Enhance your software quality with these essential rules for unit testing. From understanding the importance of unit tests to leveraging effective testing tools, these guidelines will help you create robust tests, isolate your logic, and ensure your applications perform as expected.
Customers get cranky when developers make a change which causes a bug to pop up somewhere else. One way to reduce the risk of such bugs occurring is to build a solid foundation of unit tests.
Some people aim for 100% Unit Test Coverage but, in the real world, this is 100% impractical. Actually, it seems that the most popular metric in TDD (Test-Driven Development) is to aim for 100% of the methods to be unit tested. However, in practice, this goal is rarely achieved.
Unit tests are created to validate and assert that public and protected methods of a class meet an expected outcome based on varying input. This includes both good and bad data being tested, to ensure the method behaves as expected and returns the correct result or traps any errors.
It's important that the unit tests you develop are capable of failing and that you have seen it fail. A test that can never fail isn't helpful for anyone.
This is a fundamental principle in Test Driven Development (TDD) called Red/Green/Refactor.
When you encounter a bug in your application you should never let the same bug happen again. The best way to do this is to write a unit test for the bug, see the test fail, then fix the bug and watch the test pass. This is also known as Red-Green-Refactor.
Tip: you can then reply to the bug report with "Done + Added a unit test so it can't happen again"
There are three main frameworks for unit testing. The good news is that they are all acceptable choices:
- They all have test runner packages for running tests directly from Visual Studio
- They all have console-based runners that can run tests as part of a CI/CD pipeline
- They differ slightly in syntax and feature set
Check out these sources to get an understanding of the role of unit testing in delivering high-quality software:
A Continuous Integration (CI) server monitors the Source Control repository and, when something changes, it will checkout, build and test the software.
If something goes wrong, notifications are sent out immediately (e.g. via email or Teams) so that the problems can be quickly remedied.
Test Projects
Tests typically live in separate projects – and you usually create a project from a template for your chosen test framework. Because your test projects are startup projects (in that they can be independently started), they should target specific .NET runtimes and not just .NET Standard. A unit test project usually targets a single code project.
Project Naming
Integration and unit tests should be kept separate and should be named to clearly distinguish the two. This is to make it easier to run only unit tests on your build server (and this should be possible as unit tests should have no external dependencies). Integration tests require dependencies and often won't run as part of your build process. These should be automated later in the DevOps pipeline.
A test verifies expectations. Traditionally it has the form of 3 major steps:
- Arrange
- Act
- Assert
By difficult to spot errors, we mean errors that do not give the user a prompt that an error has occurred. These types of errors are common around arithmetic, rounding and regular expressions, so they should have unit tests written around them.
- Do you know why unit tests are important?
- Do you know what unit tests to write and how many?
- Do you make sure that the test can be failed?
- Do you write unit tests to confirm bugfixes? (aka Red-Green-Refactor)
- Do you know the most popular unit and integration testing frameworks for .NET applications?
- Do you know good sources of information to get started with Unit Testing?
- Do you have a Continuous Integration (CI) Server?
- Do you follow naming conventions for tests and test projects?
- Do you know how to structure a unit test (aka the 3 a's)?
- Do you have tests for difficult to spot errors (e.g. arithmetic, rounding, regular expressions)?
- Do you run Unit Tests in Visual Studio?
- Do you isolate your logic and remove dependencies on instances of objects?
- Do you have tests for Performance?
- Do you Health Check your infrastructure?
- Do you isolate your logic from your IO to increase the testability?
- Do you reference the issue ID when writing a test to confirm a bugfix?
- Do you test your JavaScript?
- Do you use Live Unit Testing to see code coverage?
- Do you write integration tests to validate your web links?
- Do you unit test your database?
- Do you use IntelliTesting to save you in testing?
- Do you use subcutaneous tests?
- Do you use IApiMarker with WebApplicationFactory?