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:
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.
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.
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:
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.
When you build the test project in Visual Studio, the tests appear in Test Explorer. If Test Explorer is not visible, choose Test | Windows | Test Explorer.
If there are complex logic evaluations in your code, we recommend you isolate them and write unit tests for them.
Typically, there are User Acceptance Tests that need to be written to measure the performance of your application. As a general rule of thumb, forms should load in less than 4 seconds. This can be automated with your load testing framework.
Most developers include health checks for their own applications, but modern solutions are often highly dependent on external cloud infrastructure. When critical services go down, your app could become unresponsive or fail entirely. Ensuring your infrastructure is healthy is just as important as your app.
If your method is consists of logic and IO, we recommend you isolate them to increase the testability of the logic. Take this for example (and see how we refactor it):
Some bugs have a whole history related to them and, when we fix them, we don't want to lose the rationale for the test. By adding a comment to the test that references the bug ID, future developers can see why a test is testing a particular behaviour.
The need to build rich web user interfaces is resulting in more and more JavaScript in our applications.
Because JavaScript does not have the safeguards of strong typing and compile-time checking, it is just as important to unit test your JavaScript as your server-side code.
By enabling Live Unit Testing in a Visual Studio solution, you gain insight into the test coverage and the status of your tests.
Whenever you modify your code, Live Unit Testing dynamically executes your tests and immediately notifies you when your changes cause tests to fail, providing a fast feedback loop as you code.
Note: The Live Unit Testing feature requires Visual Studio Enterprise edition
If you store your URL references in the application settings, you can create integration tests to validate them.
We've all heard of writing unit tests for code and business logic, but what happens when that logic is inside SQL server?
With Visual Studio, you can write database unit tests. These are useful for testing out:
These tests can also be added to the same library as your unit, web and load tests.