I’ve been looking for language to help articulate the difference between two styles of unit testing, and really like how Martin describes it in Mocks Aren’t Stubs.
Mocks use behaviour verification.
Classical uses state verification.
|Like working with real objects||Prefer working with fake objects|
|State verification||Behavior verification|
|Use mocks to test collaborations||Use mocks all the time|
|Will hard code collaboration||Will mock collaborations|
|TDD from middle out||TDD from the outside in|
|Use ObjectMothers/Factories for test setup||Will mock only what they need for test collaboration|
|Test tend to be more coarse grained – approaching more integration style tests||Tests tend to be very fine grained – may miss integrations|
|Classists don’t couple tests to implementation||Mockists do|
|Classists don’t like thinking about implementation when writing tests||Mockists do|
|Don’t mind creating query methods to support testing||Mockists typically don’t have to|
|Classists style can encourage Asking Not Telling design||Mockists style encourages Tell Don’t Ask|
Classical vs Mocking
I generally prefer the classical style to the mocking mostly because I don’t like thinking about implementation when writing a test.
I do however like having the mocking option in my back pocket, for those cases when a system is difficult to test, an object has a nasty setup, or I am refactoring legacy code.
The other piece of advice I recommend (my ex colleague Jonas Claesson turned me onto this) is to keep your calculations separate from your orchestrations.
If you can do this you get the best of both worlds. Calculations can be classically testing. Thin orchestrations mocked. Should keep both set easy to read, and easy to setup.
Here are some more summary notes from Martins article.
- state verification
- behavior verification
Classical and Mockist Testing
- Classical style uses real objects
- Mocking style is to do everything with mocks
- Classicists will use mocks for creating doubles
Choosing between the differences
- If collaboration of objects is easy – classic is obvious choice
- If collaboration between objects is awkward – mock
- No big choice to be made here
- Where things get more interesting is when we talk TDD
- Mockist will TDD there systems from the outside in
- They will start with the GUI, describe their collaborations, and progressively marching deeper and deeper into the stack one layer at a time
- Classical TDD doesn’t offer that kind of guidance
- Whenever a classist requires something from a collaborator they simply hard code it
- But classists take a slightly different approach – middle out
- Here a classist will create an object for whatever domain they need and once they are working then you layer on the UI
- Doing this you may never need to fake anything
- Focuses the attention on the domain model, which helps keep the logic from leaking into the UI
- Classic TDD needs to create SUT with all it’s collaborators
- Mockists need only create SUT and mocks for immediate neighbours
- Classics will do this using ObjectMothers – reuse whole objects
- Mockists will say this is more work
- Classists will say we do this once and reuse – you setup every time
- If you introduce a bug mocking, only those tests whose SUT contains the bug will fail
- In classic, any tests of client objects also fail – ripple effect throughout system
- Mocksts will say make finding bug harder to find
- Classists say its usually pretty obvious – little bit more noise
- Classic tests can be more coarse grained than mockists as they test overall interaction of objects
- In essence classic xunit tests are not just unit tests, but also mini-integration tests
- Classists like this
- Mocksts lose that quality – also run the risk that the mocks are incorrect masking inherent errors
- Which ever way you go you need both – coarse and fine grained
Coupling Tests to implementation
- Mockists couple their tests to implementation
- Classiss don’t care about implementation – only the final state – not how it was arrived
- With mock testing, writing the test makes you think about the implementation behavior
- Mocksist like this – classists dont
- Coupling implementation also interfere with refactoring, since implementaiton changes are more likely to break tests than classic testing
- Classist are more comfortable creating query methods to support testing
- Mockists don’t have to do that as much
- Mocking also encourages Tell Don’t Ask
- Classis can encourage Asking Not Telling – though in practice this is easily hadnleld
- Mocking can also result in more classes (interfaces in various languages)
- May not be as true now, but i remember many more interfaces and classes being ccreated than was necseary when mocking
- Also DHH dislikes the designs mocking sometimes creates – mockists like it
Classic or mockist?
- I have never seen a reason to go all mock
- Mockists focus solely on implementation details of the SUT – never felt nature to me
- Don’t combine logic with orchestrations
- Logic classis – classic mock
- Orchestrations – simple as possible, no calcs, mockist
Links that help