1. A component can depend on multiple components. The dependencies along with the public interface of such component provides us with both control points and observation points.
2. Controls points include the set of public methods provided by the SUT which tests can use to interact with the SUT, and return values (indirect inputs) of the set of method calls made from the SUT to its dependencies (if we use stubs to control the return values)
3. Observation points include return values of the set of public methods provided by SUT, which tests can use to inspect the current state of the SUT, and method calls made from SUT to its dependencies (indirect outputs) (both the method invocation itself and the parameters can be observed).
4. Stub is used primarily at control points, whereas mock/spy is mostly used at observation points. The implication of this is that stub is used to control return values and mock/spy is used to verify interaction between SUT and its dependencies.
5. The definition of stub and mock are taken from the book xUnit Test Pattern.
6. In most cases, an object functions by either changing its internal state or interacting with its dependencies. In the former case, we can implement mechanisms to inspect its internal state to facilitate testing. In the latter case, we need some dependency installation mechanisms to be able to inject a particular dependency under our control that will be used by SUT instead so that we can either control the indirect input or verify indirect output of the SUT. Another implication of how objects work is that we end up with state verification and behavior verification at the same time.
6. In most cases, an object functions by either changing its internal state or interacting with its dependencies. In the former case, we can implement mechanisms to inspect its internal state to facilitate testing. In the latter case, we need some dependency installation mechanisms to be able to inject a particular dependency under our control that will be used by SUT instead so that we can either control the indirect input or verify indirect output of the SUT. Another implication of how objects work is that we end up with state verification and behavior verification at the same time.
7. Therefore, loosely, good testability means that we can easily inspect the internal state of SUT and observe interactions between SUT and its dependencies (This is how we test a class/component after all).
8. There are mainly three approaches in an decreasing order of superiority to build in a substitutable dependency mechanism:
- Dependency Injection (Constructor injection is most preferable, property injection only with optional dependencies)
- Test-specific subclass (to replace one dependency with test doubles by overriding SUT's factory methods to create dependency)
- Test Hooks (last resort, annotations visiableForTesting, only temporary solution)
- Test Method: methods that contain test logic
- Testcase Class: a class that contains test methods
- Test Runner: a class that runs out tests
- Test Fixture: "everything we need to have in place to exercise the SUT" (including SUT instance and all its dependencies)
- Test Suite: a composite of test methods/suites
- Each test method is converted to a command object with a run() method at runtime.
- A test suite is simply a composite of test method/test suites
No comments:
Post a Comment