tl;dr – Unit testing business logic builds confidence in your code, fosters change, and supplies a way to passively create documentation.
When you first start unit testing you want to write tests for everything. New applications are easy because you can simply perform test driven development to guide the process. But writing a test suite for a legacy application can be pretty daunting.
I remember staring at my screen for what seemed like hours trying to figure out where to begin. Most of the time I got stuck deciding which classes I thought were important enough to test. This was a misguided effort, of course, and I eventually learned that there are some rules we can follow.
For instance, you want to have tests in place before you start refactoring. Refactoring is a common task and its a wonder to me how many developers don’t back their changes with even the simplest of tests. I mean, come on, it’s a cover your ass move that you can literally automate.
Another easy rule to follow is to always test new code. Some applications seem too far gone to be worth the effort of unit testing. While I think there is hope for even the worst code bases, you usually get a better return on interest by testing new code first. Once your test suite has a good base, you can expand by slowly tackling the hard to test stuff.
These are great rules to get you started but eventually you have to deal with the leftovers. And in a large application the leftovers can be vast. There must be some definitive way to assign significance to classes and functions. Lets talk about unit testing business logic. But first a cautionary tale.
Unit Testing Business Logic: A Cautionary Tale
When I worked for the City of Miami I wrote a shift management application for the local Fire Department. Firefighters used the application to request shift coverage from their co-workers. A request could be made between any two firefighters as long as they held the same rank.
Development went smoothly up until the very end when I was forced to shoehorn a last minute business rule into the project. I wasn’t happy with the rushed implementation but I was satisfied that I had gotten it done. That is, until my first demo with the Fire Captain.
The demo went smoothly until I showed off rank validation. To demonstrate this I filled out the shift coverage form between a rookie and lieutenant (an invalid request according to the business rules).
After explaining that the system would not allow such a request I hit the submit button. Much to my dismay the interaction was processed and a success message appeared on screen.
Somehow my last minute change had broken shift validation. Unfortunately I was not unit testing business rules and failed to catch this before my very important demo.
Don’t Lose the Faith
Anyone who’s ever run a demo knows that something is bound to go wrong; its like the unwritten rule of demoing. If you’re lucky its something small and you’re the only one who catches it.
But you’ve got a real problem on your hands when your application fails to enforce a business rule. Such an oversight can generate uncertainty in both the client and users alike.
Your client will begin to lose faith in the product. He’ll become apprehensive about even the most trivial bugs. And to be honest, he’ll probably start shopping for a new developer.
Users, on the other hand, tend to be more cut and dry. They will curse your application for not doing what they expect and stop using it all together. Whether for business or pleasure, there are plenty of apps on the market that want to steal your userbase, and your users will be happy to oblige.
Don’t compromise the integrity of your code by not unit testing business logic. Business rules are what make applications worth anything at all. If you can’t ensure that they’re functioning as expected then what’s the point of having them?
Expectations Guard Against Regression
As frustrating as it may be, most business requirements inevitably change. Take the aforementioned shift management application I wrote for the City of Miami Fire Department.
The shift request feature started out with one rule: a request must take place between two firefighters that hold the same rank. Everything worked fine until one day when that rule changed.
The stakeholder wanted to introduce a way for the District Chief to override the process. As a result the rule was amended to ignore rank validation for any firefighter that held the rank District Chief.
From a coding standpoint the change was incredibly simple. So simple, in fact, that I made one small error during my hasty implementation. I accidentally flipped the condition so that the edge case allowed everyone BUT the District Chief to override the validation.
I know what you’re thinking, “what a stupid mistake!” But tell me you’ve never done something similar, especially when you were under the gun to produce. The point I’m trying to make is that by NOT unit testing business logic I made my code inflexible.
When you write a unit test you are basically enforcing an expectation, and expectations can be changed. You can assert that your code meets a certain requirement, change that requirement, and then prove that your application is continuing to meet the expectation that you have set for it.
A lack of unit tests is a lack of expectation, and you can’t really guarantee anything in a scenario like that. You only make your application susceptible to regressions thereby limiting its ability to adapt.
Tests & Documentation: Two Birds, One Stone
I hate bringing up my little blunder again but it makes for a good example of unit tests as documentation.
I’d been working on that shift management application for two months before I had to modify the rank validation logic. The business rules were still very much fresh in my mind. And even though I botched the implementation, coming up with a solution was a no-brainer because I was still familiar with the business domain.
But what if I had to make the same change a couple months after deployment? How does one re-familiarize themselves with an application’s inner workings after they’ve moved on to another project?
One approach is to simply read the source code. This is a valid tactic but it can become very time consuming, especially for more complicated, algorithmic type functions.
On the other hand, some projects come with business requirements documents that outline every last requirement to a T. These are valuable but they won’t provide details about the implementation. The developer will still have to sift through the source code after reading what promises to be a very long and boring document.
Take a look at the image below and let’s examine one of the most valuable resources available: the documentation that is created by unit testing business logic.
These results provide a clear picture of what is expected from this validation class. And the test code itself offers even more information in the way of state, parameters, and assertions.
These few simple tests not only serve as quality assurance, but they also provide documentation for the business rules. The entire purpose of this class is revealed without even looking at a bit of source code.
Reliability, Flexibility, and Accessibility
Business rules are a set of expectations that we hold our applications to. Unit testing business logic is a way to exercise those expectations to ensure that our code is meeting all of the requirements.
We can then use those tests to safe guard against regression when we make changes. And on top of all that, we also get the added bonus of a sort of passively created documentation.
So when you don’t know where to begin testing the best place to start is with your business logic. Think about the purpose of your application. Try to assign importance based on the expectations of the client and the user. And when in doubt ask yourself, “what would happen if this failed during a demo?”