Sunday 11 October 2015

Square Pegs and Round Holes


When writing unit tests one of the most important things that will determine whether we end up writing good tests or bad tests is how we deal with dependencies.
Bad tests won't properly separate a class from those it uses creating either unstable tests or tests whose results don't actually tell us anything about the health of the class supposedly being exercised. 
It is also very often the case that problems we face with dependencies when trying to test a class are indicative of bad design, testing shouldn't be difficult, but of course your all practising strict TDD and writing the tests first right?
So how can we supply dependencies during tests?
Sucking on a Dummy
A dummy object contains no useful functionality, it is not intended to be used by the class under test but simply by the wonders of polymorphism it can stand in for a dependency.
The fact that a dummy can be used during a test is a big smell, if you aren't going to use this object why am I having to supply it to you? Yes I could potentially pass null but that degrades readability and you have to know that null isn't actually going to break anything.
If your supplying a dummy to a constructor then clearly the class under test doesn't need the dependency to fulfil its role, maybe it does on occasion require that dependency but if its not essential it shouldn't be in the constructor.
If your supplying a dummy to an API then this would suggest you could make the developers life easier by supplying an overload that doesn't require the dependency (internally you can just call the original API passing null). This makes it much clearer how your API should be used requiring the developer to have less implied knowledge about how this all works.  
Faking It
Fakes appear to offer the same functionality as real dependencies but they take a short-cut or cheat in someway, for example supplying JSON from a file rather than calling a server.
Generally this is done to de-couple the code from some outside dependency and give more control over the data the code is working with, they also generally remove any performance factors from influencing how the code behaves.
There are many good situations to use fakes especially when prototyping or trying to debug specific conditions or data, unit testing is generally not one of this situations.
The implementation of fakes is not necessarily simple, this potential complication can add ambiguity to unit testing, this is something to be avoided.
The output of unit testing must be clear and unequivocal, if its red we need to fix the class under test, if its green were all good. Anything that detracts from this simplicity should be removed.    
Stubbing It Out
Stubs are essentially containers for pre-programmed responses to interactions, if X is called return Y, this is regardless of what is passed in the call and how many times that call happens.
They provide no response to APIs where nothing as been put in the container and they carry no expectations on what should and shouldn't happen.
This all leads to the important aspect of stubs, they play no role in deciding whether a test passes or fails.
Stubs should be used to fulfil dependencies where we don't particularly care how the class under test interacts with it, this will generally be because the call does not carry a high overhead and doesn't have any side effects for the system as a whole.
It maybe that we have an API that checks for some environmental condition or retrieves some configuration data, do we really care whether this fairly inconsequential method call is made 2 times or 3 times? Or are we more concerned with the fact that the business logic ends up doing the right thing?
Once again its important that if a test fails then we know for sure we have to fix something, if tests fail because of some trivial function call this detracts from that clear cut view.    
Send in the Mocks
And finally we come to mocks.
Mocks are much like stubs but with one important difference, they do play a role in deciding whether or not tests pass or fail.
Like stubs mocks contain pre-programmed responses to method calls but unlike stubs they do carry expectations on how these methods will be called. With mocks it does matter what methods are called, how often and with what arguments.
Mocks should be used for dependencies where it does matter how the class under test uses them, for example whether we make a server call once or twice or how many times we hit the database is important. 
With the other types of objects we've discussed were more concerned with verifying the output of the class under test, with mocks we are also interested in verifying how it provides that output.
Its a skill to determine the best way that mocks should be used, once again to hammer this point home failing tests have to carry a clear message, being over zealous in using mocks to enforce very strict limitations on the class under test will lead to nagging doubt that maybe the problem is with the test rather than the code being tested.
Its important that unit tests are treated with the same care and attention as our functional code base, this isn't just because were relying on these tests to give our code a clean bill of health but also because they shine a light on issues with our implementation.
When you encounter some difficulty in supplying a dependency to a class under test don't just write this off as being just one of the frustrations of writing tests, instead ask yourself what this tells you about the class your testing.
If your class is a well written loosely coupled class with high cohesion and a well thought out API maybe you shouldn't be having this problem?   

No comments:

Post a Comment