Test Doubles
Test Double
A Test Double is a software component that implement partial functionality. They are used if some component:
- is not available or implemented yet
- will result in recoverable actions (bomb effect, meaning an action that cannot be undoed)
- is expensive to run (e.g., takes a considerable amount of time)
- is too difficult to instantiate or configure.
It must be implemented with as little change as possible to the software
Some terminology to know
- stubs: a skeletal or special purpose implementation that typically provides canned answers to calls made during the test
- mocks: a pre-programmed class with ability to verify the class under test made correct interactions with them
- fakes: objects with working implementation which "take shortcuts" when needed. Can result in shorter computational time.
- dummys: objects that are passed around but never actually used. We know we won't do anything with this variable for now, so it can be something like null.
Examples of the bomb effects:
- send emails to lots of customers
- perform financial transaction (e.g., charging a credit card)
- launching a mislse
- test-driven development: tests are written and executed prior to the implmentation.
- database is not ready yet or live db connection does not exist at the time of testing
- heavy calculations that take considerable amount of time to finihs
- need user interactions.
Using and Testing with Doubles
The process of testing with doubles: Have a Test Harness (e.g., JUnit tests) --> Software under test -->
- Dependency component --> bomb, or
- Double of dependency component
Typical Workflow:
- obtain/create necessary test doubles (using mocking tools)
- specify the expected sequence of interactions with the test doubles
- carry out the action under test
- veryify that the expected interactions occured.
Tools to use to help with this:
- IDEs can automatically generate simple stubs
- Automated mock testing tools include Mockito, jMock, EasyMock, PowerMock for Java; Google Mock, TypeMock for C; unittest.mock, PyMock, Mox for python, Jest, Sinon.jest for JS.
E.g., we have a class called Order that can order from a Warehouse.
- Assume that "Warehouse" has not been implemented yet, and is not avaialble, as it is very constly to instantiate from for testing, or queries take a considerable amount of time.
- This is when it's a good idea to use a double (mock in this case).
- In our test cases, veryify the output of the ware house, verify the interaction with the collaborator is good (i.e., behavior), and verify the order of the interactions.
Comparisons
Mock vs. Stubs Stubs:
- state-based verifcation
- provides canned answers
- E.g., return 0 from a method that should return
int,nullfrom a method that should return an object, etc.
- E.g., return 0 from a method that should return
Mock:
- behavior-based verifcation (aka interaction-based verification)
- verifies certain interactions were made with the mock (in a certain order)
Behavior- vs State-based testing:
- If the dependency is available and undemanding: Use real class and state-based testing
- If the dependency is not available and/or demanding: Use real class and state-based if possible. Otherwise, use interaction-based testing
- Behavior-based testing tests the outbound calls of the SUT to ensure it talks properly to its collaborators:
- More coupling to dependencies implementations since it checks all the interactions, the order of interactions etc.
- If implementation of collaborators change (e.g., method names, method parameters etc.), behavior-based tests are more likely to break
- State-based, on the other hand, focuses on the final state:
- Checks exact output values
- Less coupling to dependencies implementations
Linked from Software Testing and Debugging