I was reading http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/ by Miško Hevery. He makes some excellent points about using statics and the effect they have on testing.
There seems to be a common misreading of the original article, where he says something like ‘if you use a static in a class, that class is harder to unit test’ and people hear ‘statics are hard to unit test’. This needs some addressing. This post seeks to explain why statics compromise testability with an example.
So, consider the development of a dictionary class with string keys. At some point you will want to hash the keys and assign the values to bins;
# pseudocode class Dictionary: Add(key, item): hash = String.GetHashCode(key) bucketIndex = hash % maxBuckets ... this.buckets[bucketIndex].Add(item)
Now, a hash function (String.GetHashCode) is a perfect candidate for a static; it is a pure function with no state to worry about. It’s also easy to test. There are no worries about whether the static is testable.
But what about our dictionary class? Before I release this on the world, I want to convince myself that I’ve got the logic right for what happens when we get a collision — when two different keys generate the have the same hash code.
With the static sitting there in the Add function, I’m out of luck. How can I guarantee a collision in a test? I’d need to know two keys which generate the same hash code, and write a test like this;
key1 = "2394820934802934820348" key2 = "lgjeibrieovmdofivevrij" dictionary = new Dictionary() dictionary.Add(key1, "A") dictionary.Add(key2, "B")
And here the dependency on the static becomes clear; in order to write a test for the dictionary, I need to know the implementation details of the static to write my test. I’ve made unit testing harder — remember, the unit is the dictionary.
So to make it testable, I need to add in a new idea into my Add method; the idea of a swappable object with a GetHashCode instance method;
# pseudocode class Dictionary: Constuctor(hasher): this.hasher = hasher Add(key, item): hash = hasher.GetHashCode(key) bucketIndex = hash % maxBuckets ... this.buckets[bucketIndex].Add(item)
Now my test becomes trivial;
class AlwaysCollidingHasher inherits Hasher GetHashCode(key): return 0 # this hasher always causes a hash collision dictionary = new Dictionary(new AlwaysCollidingHasher()) dictionary.Add("A", "A") dictionary.Add("B", "B")
Now I have a proper unit test; my dictionary can now be tested reliably for this dangerous condition, and it’s no longer relying on a particular implementation detail of another class. It’s a true unit test.
So to reiterate; the point isn’t that statics are hard to test. The point is that sometimes, things that use statics are hard to test.
Nice and clear, up to the point! Now I see why classes with statics are hard(er) to unit test.
Thanks, Ivan! Glad it was useful.
The problem isn’t really the use of a static function though — it’s the use of a non-injected dependency. Your code could be revised to fix testability while still using static functions, by changing the first line of Add to `hash = hasher(key)`, then passing in the static function String.GetHashCode as the value of hasher.
The problem isn’t really the use of a static function though — it’s the use of a non-injected dependency. Your code could be revised to fix testability while still using static functions, by changing the first line of Add to `hash = hasher(key)`, then passing in the static function String.GetHashCode as the value of hasher.
Russel is right.
For unknown reason your Dictionary constructor has a parameter for the hasher while the static function has none. To be fair you should extend the static function too:
dictionary.Add(new AlwaysCollidingHasher(), key1, “A”)
dictionary.Add(new AlwaysCollidingHasher(), “B”, “B”)
Then there is no problem anymore for testing. Still static functions should be stateless, so better make something like this:
SomeNameSpace.Add(dictionary, new AlwaysCollidingHasher(), key1, “A”)
SomeNameSpace.Add(dictionary, new AlwaysCollidingHasher(), “B”, “B”)
Sorry there is a typo in my post. Please replace key1 by “A”.