VF Bath
Studio 1, The Glove Factory
Brook Lane
Holt
Wiltshire
United Kingdom
BA14 6RL
VF Lisbon
Av. 24 de Julho
4 4º Esq.
Lisboa
Portugal
1200-480
VF Philadelphia
1801 N Penn Rd
Hatfield
PA 19440
USA
VF Canada
84-86 Elizabeth Avenue,
Suite 101,
Regatta Plaza II,
St. John's,
NL A1A 1W7
Canada
VF London
35 Artillery Lane
London
United Kingdom
E1 7LP
VF Warwick
Unit 6, Olympus Court
Olympus Avenue
Tachbrook Park
Warwick
United Kingdom
CV34 6RZ
UX Design
Software Development
Testing
Design Sprints
Adobe XD Migration
Cloud Hosting & Services
DevOps
Cloud Services Consultancy
Microsoft Dynamics 365
Data Architecture
Power BI
Data Visualization
Power BI Resourcing
Splunk
Mobility Data
28
Jun
What is unit testing and what are the benefits?
Paulo MartinsBrief
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 practices. Test projects should follow the same folder organization as the tested project and are usually named ProjectName.Tests. Keep in mind that as your project grows you might also want to write integration tests and in which case a better naming scheme would be ProjectName.UnitTests and ProjectName.IntegrationTests.
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:
In the class constructor, we’re creating a stub of the inventory repository, called in the method we are testing. This is where the importance of a good architecture comes into play: this dependency is supplied to the InventoryService class via dependency injection and as such, we can easily create a stub of the repository layer, thanks to mocking frameworks like Moq. Keep in mind of the difference between a mock and a stub described by this excellent article by Martin Fowler: https://martinfowler.com/articles/mocksArentStubs.html
When using xUnit, test methods use the Fact attribute. There are also Theories which are parameterized tests for when we want to test the same method with multiple inputs. You can read about this in more detail on the official documentation.
We use the Arrange-Act-Assert pattern to clearly separate our steps:
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!
Contact