Test Isolation
Test should only fail because of the system under test.
Consider two cases. In the first case, you are testing your code under test which is independent. Now, if an error occurs in your test, you'd know it was because there is something wrong with your code.
Second case - your code depends on some other class or code. Now, if you get error in your unit test, you wouldn't know where the error is coming from. This makes the whole exercise of creating a unit test pointless. You'll still have to investigate which part is causing the error in your machinery despite the test.
How to ensure test isolation
Find a Seam
Seams depend on the use case
- Unit Tests want no dependencies
- Integration/system tests might only isolate a few dependencies
- If you have a small component where three classes that interact very closely, you might not need isolation there because they belong to the same logical problem.
- Anything that depends on time or duration
- You can't wait till February 29th to execute your test
Provide Testability Always create classes with interface. That way, if your class is a dependency for another code, the developer can create a test double by simply implementing that interface.
> Create interfaces for all your classes. Always.
Interfaces are the heart of testability.
The users of your code will be grateful.
Provide an Dependency Injection mechanism
Implement the interface of the depended-on component
- Interface provided → you are all set
- No interface provided
- → Convince your colleague to provide an interface
- → Implement your own interface
- Only implement the interface methods you need for the test using the keyword
PARTIALLY IMPLEMENTED
- Only implement the interface methods you need for the test using the keyword
Inherit from the depended-on component
- Redefine the methods where you need to have a test double for
- Drawbacks
- Not always possible due to [[programming.oo.components.types.final]] definition of the class - The risk to execute unwanted code in tests is higher e.g. when methods are added later in the code under test and are forgotten to be REDEFINEd in the subclass
- Implement the test double
Dependency lookup using Factory
- During tests, the object factory shall return test doubles to the code under test
Secure injection
As a test developer, you need a secure technique to inject test doubles into the object factory class
Solution - Injector class
- Must only be available for tests. It is therefore declared as test class
- Must be global friend of the factory class to modify object factory class internal
Example
CLASS cl_managed DEFINITION
PUBLIC
FINAL
CREATE PRIVATE
GLOBAL FRIENDS cl_factory.
PUBLIC SECTION.
INTERFACES if_managed.
…
CLASS cl_factory DEFINITION
PUBLIC
FINAL
CREATE PRIVATE
GLOBAL FRIENDS cl_injector.
PUBLIC SECTION.
CLASS-METHODS get_managed
RETURNING VALUE(r_instance)
TYPE REF TO if_managed.
PRIVATE SECTION.
CLASS-DATA g_managed
TYPE REF TO if_managed.
…
CLASS cl_factory IMPLEMENTATION.
METHOD get_managed.
IF g_managed IS NOT BOUND.
g_managed = NEW cl_managed( ).
ENDIF.
r_instance = g_managed.
ENDMETHOD.
...
CLASS cl_injector DEFINITION
PUBLIC
FOR TESTING
FINAL
CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS inject_managed
IMPORTING i_test_double
TYPE REF TO if_managed.
…
CLASS cl_injector IMPLEMENTATION.
METHOD inject_managed.
cl_factory=>g_managed = i_test_double.
ENDMETHOD.
…
Consider a Factory for integration tests that manages your application / component indirections and collects all factory methods.