Unit testing is an important part of software development which is often overlooked since, at first glance, it doesn't seem to directly impact project development.
Besides the time spent training the development team to write unit tests, there is also the time spent maintaining those which will inevitably break as the codebase evolves. It might seem like a lot of work. So why should we bother?
Unit testing is a type of a software testing method where small components (units) are tested to determine if they behave as expected. These tests should not cross their own unit boundary as this turns them into integration tests - another important testing method, with its own advantages and disadvantages.
There are a number of key benefits to unit testing. As a project grows in size and complexity, it becomes increasingly hard to modify a part of the system without the risk of another part breaking. And while unit testing is not a silver bullet, it provides us with many advantages:
Now that we know what unit testing is and why it’s essential, let's look at how can we apply it on a .NET Framework project. We are going to use xUnit.net as the testing tool, Moq as the mocking framework and Autofixture to generate objects for us.
Let’s say we have a music store where we save information about the album, artist, available stock and format (CD, Vinyl, digital). A store employee can create, delete and modify existing entries in the store. Let’s say we want to test the following method that retrieves the album ID:
In this simple case, we retrieve an entry from the repository and have 2 possible outcomes: either the entry is null or is not null, in which case the entry is returned.
The unit tests we write serve as documentation for the system, so naming and organizing the test projects should follow best practice. Test projects should follow the same folder organization as the tested project and are usually named ProjectName.Tests.
This particular method is contained in the InventoryService class, so let’s create an InventoryServiceTests class in our newly created test project. This will contain all the tests for the InventoryService class.
When it comes to naming tests, there are multiple approaches, all with their own strengths. I personally use MethodName_StateUnderTest_ExpectedBehavior as it’s pretty descriptive, and makes it easy to find the method being tested.
Now we’re ready to create our first test. Remember that in our case, the method being tested can either return the entity with the given album ID, or throw an exception, if the album ID isn’t valid. So let’s test for both scenarios:
After running the test, it successfully passes. Let’s move on to the next scenario:
Modifications to the class illustrate some important points:
After running our newly created tests we can see that our code coverage is 100%. This isn’t always possible to achieve in the real world due to time constraints. For this reason, you should always give priority to critical paths when implementing unit tests. It’s also important to give unit test to fixed bugs, to prevent regressions.
Unit testing is a learning curve and may take some time to get used to. But combining different tools such as xUnit, Moq and AutoFixture can make creating and maintaining unit tests a breeze.
Happy testing!
Have a project in mind? No need to be shy, drop us a note and tell us how we can help realise your vision.