What Should Be Unit Tested?
Since the popularity of unit testing has grown in recent years in the .NET community, there has been some confusion about testing. There are several philosophies about when to write tests. There are also varying opinions about if you should write tests or not. I’m not going to address either of those things here. I simply want to explore the concept of unit testing, and more specifically what parts of our code should have unit tests.
What is a Unit Test?
I have heard the word unit test thrown around a lot in varying contexts. I have heard quality assurance professionals telling software developers that they need to unit test code before delivery. In that context they typically mean manually running the application to verify that it does what it’s supposed to. I’ve heard developers refer to any and all automated tests as unit tests. This can be very confusing. I think in those two scenarios the problem is people were using the word unit where it didn’t belong. The term unit has also been debated among people in the software community.
In the context of unit testing software. A "unit" is an isolated behavior in a software program. In my definition, isolated means that we can validate the expected outcome of the behavior by directly executing the behavior and observing the results of that execution without requiring external dependencies.
Most of the code that we write that could cause potential issues contains logic. That logic usually has a few different aliases: business logic, rules, the engine, domain, a feature. Another alias for this type of code is a program. This code is what most software developers are actually paid for. This code is what we should be securing, and fortifying. We should make sure that this code is readable and maintainable. We should make sure this code is properly encapsulated. This code should have automated tests.
Architecture and the Business
A typical architecture for the last 15 years or so has been three tiered. A User Interface layer, and Logic Layer, and a Data Layer.
These are supposed to be distinct layers. The UI Layer depends on the Logic Layer, and the Logic Layer depends on the Data Layer.
While looking at these layers ask yourself the following questions.
- Which one of these do you think is the most important?
- Which part would you be afraid of changing the most in fear of breaking things?
- Which of these layers might have code that we would want to share in other applications?
- Which of these layers are most software developer paid to maintain?
- Which layer is the business relying on to be as reliable and accurate as possible?
I hope you answered the Business Logic Layer. If there is incorrect logic in the business logic of an application that could potentially cost your company money. As a software developer your job is to help the business be more efficient. That is why we write applications.
Most businesses these days rely heavily on software to even operate their businesses. Part of your responsibility as a professional software developer is to help the business you work continue to operate. Besides just being operational, it would prefer to be efficient. Do you think buggy software helps or harms the goal of business efficiency? Can you think of ways to avoid writing bugs in your software?
There is a saying about software, it can be have a lot of features, be built quickly, or be cheap. Pick two. In my experience, time is always the thing that gets sacrificed. Scope is rarely sacrificed, and well cost is usually somewhat fixed. So imagine you have a lot of features to build for an application, and you want as few bugs as possible. That is your responsibility to make a reasonable effort to deliver bug free features on a particular schedule. Given you are using the three tiered architecture we discussed above where would you likely start, and how would you try to limit the number of bugs?
The rule of one
When I mentioned behaviors tested in isolation before, I think I forgot to mention the rule of one.
- One Logical Execution
- One Expectation Verified
When we write unit tests, we want to limit our expectations to one thing. We also want to limit ourselves to one piece of logic being executed. Meaning one API call to perform the execution. Calls to create inputs are not included in this limit. In this way we can be sure if a test fails we know why we believe it failed. This one expectation was not verified. It was incorrect.
If we execute more than one public API call, with one expected result then we may not know which of the calls, met or did not meet our expectations.
Also if we have multiple expectations in the same test, it might be difficult at a glance to know which one of our expectations was not met in a failed test.
Both of these can also make for brittle tests. If we decide to change one expectation of the results of an API call that shouldn’t have an effect our other expectations.
How much code should you test?
At least test one expected case for each of the business logic’s intended public API. You can do this as you develop the software or after, but it should be done.
What Should Not Be Unit Tested?
I could make a long list of things that couldn’t be unit tested, but for now I’ll start with a short list.
- User Interfaces
- Data Access
In the .NET realm there are a few frameworks that are frequently used. The first is the .NET framework. It is unlikely that you would have a good reason to explicitly write a unit test for a .NET Framework API. I think you can be confident it will do what you expect it to in almost all scenarios. You might write some throw away tests to explore how a particular API works, but those aren’t likely tests you would want to keep around as apart of your test suite. Another example of a framework many .NET developers use is Newtonsoft.Json.Net. That framework is the most downloaded nuget package ever, and you better believe they have unit tests for it. Even if you don’t completely trust a third party framework with your career, you still shouldn’t be unit testing it. You should only write unit tests for the software logic you or your company owns, and is responsible for delivering. Frameworks should have unit tests, but it is the responsibility of the maintainer of the framework to write them.
I often see examples of people trying to unit test User Interfaces, this is typically in the category of a framework. For example in .NET MVC people will try to unit test at the controller level. They’ll new up a controller and call some action on it, and validate that something happened. This seems rather odd to me, because the logic they want to actually verify is often in a service that is being called in the controller. That service is usually injected into the controllers instructor and thus means this isn’t a unit test at all since the controller’s action isn’t isolated. It require’s a specific dependency to be able to test it’s proper functionality. Often in the MVC world the result of calling a method on a controller is a some kind of View also, which is considered the User Interface. So that breaks two rules.
In the MVVM pattern people might try to unit test their View Models behavior as well. This has similar issues as the controller. View Models are bound to a view. That is typically done using some kind of framework. Also the view models methods are typically just delegating logic to a serivce of some sort just like the controller does in MVC.
Whether attempting to unit test code in a View Model or a Controller you are really attempting to exercise some Business Logic Layer functionality, so why not just do it directly. When unit testing you should not assume there is any user interface at all. There is only a machine level interface, which is your public API.
I don’t think I need to say much about unit testing data access. Data access almost always involves a third party framework, and if it doesn’t work you are not going to be in good shape anyway. Why would you right unit tests that exercise the .NET framework or an ORM Library. I think one argument would be that you want to make sure your sql works. There are a few ways you might do this. You could pass raw sql to your data access library, and verify it gives you back results you want. The other way would be using an ORM. There are two violations here. The first is that you are testing a framework, the second is you are testing the data access layer.
One excuse, I often hear about unit testing is we don’t have time to do that. To me that means we have more time to fix bugs than we do to write bug free software. I don’t think any business owner would agree with that statement. I don’t mean to imply that unit tests will make bug free software, but it should drastically reduce the amount of bugs in your software. I hope after reading this article you have a little bit better understanding of what a unit test is and where you should write them. In a future article I’ll give some more concrete examples of how to write good unit tests.