In this article, I’ll propose a C# solution to a common testing problem with enums using a special NUnit attribute. I’ll also introduce you to a related attribute which can expand your tests.
Recently I wrote some thoughts on code coverage. In that article, I discussed a method that switched over enum values to return a string and how the method will break if the enum expands. That article got me thinking about how we should be testing things like enums.
Let me walk you through my solution.
To do a quick recap of my prior article, the problem is if you have a switch statement like the following:
In this case, if we ever add a new enum value, that enum will return “ow” by default. That might be fine for a game project, but imagine a scenario where you needed to display a user-facing string in a business application.
Even if we had the application throw a new error on the default case, there’s no way to conclusively ensure that the method actually gets tested during development and testing before being released to production.
Since I’m all about making it impossible for various types of defects to ever exist, let’s look at my current preferred solution for this.
The NUnit testing framework is currently my preferred unit test library for C# development. XUnit is close behind, but I find NUnit’s attribute naming more intuitive and it offers some capabilities that XUnit does not.
One of those capabilities is the use of a
Values attribute. This is an attribute that can be used to decorate a test method like so:
This code result in 3 different tests at time of execution – one that uses an
1, then another for
2, and finally one for
3. While I would tend to use
TestCase attributes for this specific scenario, this example should tell you a little bit about the
Values attribute in NUnit.
What’s really cool about
Values is that you can apply it to enums. Take a look at this code:
This unit test will run once for every member defined in
DamageType and pass that value in as
Put another way – if we ever add a new
DamageType, the test will automatically gain a new test and verify its behavior. While this might not look like much, it helps ensure that unexpected errors are not occurring with new enum values.
I actually put this type of technique to use today in my day job on two large enums and was able to find a number of failing cases that we had no idea were even lurking deep in the code. Not only did adopting
Value attributes help prevent future bugs, but it exposed current bugs we didn’t even know we had!
While I’m not sure why you’d ever want to do this, the
Values attribute can be applied to
boolean parameters, allowing NUnit to automatically inject both true and false values. Personally, I’d rather use explicit
TestCase attributes for readability, but this is something you can do if you want to.
More practically, NUnit offers a
Range attribute which works similarly to the
Values attribute, but generates a range of possible values.
Let’s say you wanted to test some percentage math:
Range attribute lets us define an inclusive range from a low value to a high value. This will then pass in all integers between 0 and 100 to this test method as
percentWhole on a unique unit test variant.
This specific example might be good for finding boundary issues at 0 or 100, or for exposing issues with various areas on rounding around 33% or 66%.
Hopefully I’ve convinced you that
Values and potentially
Range are worth investigating if you’re doing .NET unit testing.
While I should caution you that if you have a large number of unit tests, you’re paying for the overhead of NUnit jumping from one test to another. In most projects and scales this won’t be an issue, but if you habitually use
Values attributes, you could get to some large sizes fairly quickly.
The range-based technique is now my preferred and standard way of testing enums going forward, because it helps mitigate the risk that I’ll forget to test something.
What other testing techniques have you investigated and enjoyed?
The post Future-proofing .NET Tests with NUnit Values Attributes appeared first on Kill All Defects.