mentis vulgaris
simple thoughts | jason smith
Encapsulating Your Way To Better Unit Tests
Posted by Jason Smith - 07/02/10 at 11:02:01 amI an earlier blog, I discussed the qualities of good unit tests. Primarily, good unit tests are fast, isolate the bugs, repeatable, self-validating and timely.
At first blush, that seems easier said than done. We write distributed software and connect to databases. Some of those databases live in another country.
So, how can an object that sets up connections to a database, prime it with test data, consume that data, manipulate it and then present it in a UI actually be fast, isolating, repeatable and self-validating? If by fast we mean microseconds per test (that’s exactly what we mean), we’re toast as soon as we even talk to the database. How do we isolate our tests when we write to a persistent store, while another test is running in parallel against the same store? What happens when the database fails for some reason, and the data has been partially changed? Is the error in our code, or the database? And self-validating? Come on! Our UI’s need to be poked, prodded and looked at to be tested.
Unit-testing Nirvana sounds like a ton of work.
Actually, it’s the work we already do.
Most of us use object oriented languages because we buy into the idea of encapsulation — or, said another way, we want to reduce couplings to particular implementations. Even if polymorphism and inheritance doesn’t float your boat, old-fashioned structured design pushes this same idea. We simply hide (encapsulate) implementation behind function calls, so we can focus on the work at hand.
For example, assume we have a window where individual elements within it may be visible or invisible based on user preferences. What is it about the algorithm to determine the initial visibility for elements of a window that requires a database or a .config file? Nothing, right? Either will approach work. So will other changes in the underlying program. All the algorithm really cares about is it gets configuration data from somewhere. So we should encapsulate that concept.
We know our window needs to be initialized with some configuration data. The window doesn’t really care where it came from. A candidate method might look something like this:
class MyWindow : System.Windows.Forms.Form
{
// a list of our window controls that we can enable or disable visibility on.
private List<Control> _toggleVisibility;
// ... blah blah
public void SetInitialWindowVisibility(IDictionary<string , bool> visibilities)
{
foreach (var control in _toggleVisibility)
{
if (visibilities.TryGetValue(control.Name, out visible))
{
control.Visible = visible;
}
}
}
}
We honor the Single Responsibility Principle: No database involved. No System.Configuration.ConfigurationManager. If high cohesion and low coupling are the benchmarks of good design, then this is pretty decent.
Here’s the payoff — this approach gave us a good unit-testable design for free:
- it’s fast — for our test, we simply pass a preloaded dictionary and verify the window controls’ visibilities match the dictionary state
- it isolates the error — if the unit-test fails here, it’s because the visibility setting behavior was broken. Period. Not a database, not a parser — just the method under test.
- It’s repeatable — every time we run it, we get the same results — noone on the outside can poison the test during it’s run,
It’s self-validating — our unit test need only iterate over the embedded controls collection comparing the state of the control.Visible value. No popping up the user-interface to hand inspect is necessary.
Sure, that configuration dictionary needs to be initialized somewhere, and that code should be tested as well. But if it connects to a database to get the information, keep the test for that code outside the body of unit-tests (recall, unit tests should be run on the developers’ machines, often. If they aren’t fast, they won’t be run at all). Definitely include database tests in the an integration or smoke test, though — those are run with much lower frequency, so they can afford to take a bit more time to run.
For the adventurous, you can raise the bar even more – what about this algorithm requires that it be implemented in a System.Windows.Forms.Window derivative? Can the implementation be refactored to remove this dependency?
No Comments yet »
RSS feed for comments on this post. TrackBack URI
Leave a comment
Powered by WordPress with GimpStyle Theme design by Horacio Bella.
Entries and comments feeds.
Valid XHTML and CSS.