How To Come Up with Test Cases for Unit Tests
Two simple approaches to coming up with test cases for unit tests with examples in C#.
Time to read: ~3 minutes
Published: Sunday, November 14th 2021
Table of Contents
Unit tests are an important part of the development process that help to reassure you that your code is doing what it's supposed to be doing. A unit test tests a specific "unit" (as the name implies) of your code, which in my experience ends up being a specific method or function. If you are able to verify all of the individual units of your application are operating as expected, you can feel a bit safer about your application as a whole.
A single method could have any number of valid test cases; it ultimately depends on the structure of the method. To help determine the test cases required to test a method, there are two simple approaches:
I will help demonstrate these approaches by providing real code examples. All code snippets will be written in C#, and tests will make use of my favourite package for unit testing, Fluent Assertions.
We will be coming up with tests cases for the following method:
This method takes in a collection of items as a parameter. It goes through the list of items and for each
Item it is checked whether it has a
Name that is null or empty. If there are any items without a
Name defined, we return
True, otherwise we return
The Parameter Approach to coming up with test cases involves thinking about what values the parameters for a method can be.
Looking at the parameters of the method
ContainsNamelessItems, we have a single parameter of type
items. This parameter could have several possible values:
itemscontains at least 1
Itemthat has a
Nameproperty that is not defined
itemsdoes not contain an item that has an undefined
Each of these possibile values can be used to come up with a test case. To come up with our assertions for the test case, we need to consider what we expect to happen when the parameters have each value.
Here are some possible test cases and a corresponding test that would verify the test case:
List<Item> is empty, we expect the return value to be
False because there are no items in the
List<Item> that are nameless.
List<Item> contains at least 1 nameless
Item, we expect the return value to be
True because there is a nameless item.
List<Item> does not contain any items that are nameless, we expect the return value to be
False because all items have a name.
null, we expect an
ArgumentNullException to be thrown
(This will occur becase we are calling
items.Any() when items is null)
The Path Approach to creating test cases involves going through the method under test and finding all of the different paths of execution.
The method we defined above has a single path of execution as there are no conditions driving the path anywhere but straight to the end of the method. To alter the path, we would need to introduce some kind of condition, whether through an
switch, or a
try/catch statement. Inside these conditional blocks, there is an opportunity to return or throw in a position that is not the end of the method.
Let's demonstrate this by introducing a new path into the method
Say we don't like the
ArgumentNullException that is thrown when we call
items is null, and instead we want to throw an
ArgumentException with our own custom message. To do this, we will have to add a condition to the method that checks if the list of items is null.
Here is a flow diagram illustrating the effect of introducing this condition:
Now instead of making it to the end of the method, there is a possibility of exiting earlier if items is null, thereby creating a new path of execution.
Here is what that would look like if it was implemented:
And a corresponding test for this test cased would look something like this:
Something to note about the introduction of this new conditional statement is that the test that verified that an
ArgumentNullException was thrown given a null collection will now fail since we handle null items in the new if block instead of when we call
By using both the Parameter and Path-based approaches together, you should be able to come up with a good number of test cases that will help you verify that your methods are functioning as expected. There will definitely be more cases that you should cover that are more application-specific, but these approaches will give you good starting point for testing the individual units of your application.
Do you have any other ways you come up with test cases for your unit tests? I'd love to know, so be sure to leave a comment below.
Thanks for reading, now go build some stuff!