Main Takeaway
If possible, classical testing is the ideal testing strategy. Testing doubles simplify and allow systems to be tested in isolation when classical testing is slow, unpredictable or complex. Though unit tests using test doubles can be easy to write, the inappropriate use of testing strategies will reduce developer productivity and not be efficient at catching bugs.
Overview
Unit testing should be a quick and cheap way of testing systems in isolation during development
Integration testing is a good way of testing the real functionality of a system due to the reduced use of testing double techniques used. Code that is used in production will be run and will catch more bugs than unit tests.
Testing Principles
Testability: Ease of writing unit tests for code e.g ease of using test doubles (Should be considered when writing code - else refactors have to be made later)
Applicability: Inappropriate use of test doubles may reduce engineering productivity, using the real implementation is sometimes the best option
Fidelity: The closeness which the test double mimics the real implementation. No value if the test double has low fidelity. Larger scope tests need to supplement unit tests which use test doubles.
Testing Double Techniques
Testing double techniques can be used in unit tests to isolate the system. Mocking frameworks enables stubbing and interaction testing. Faking can also be in unit tests
Stubbing: Giving a dummy class functions which are required in the test and telling it how to perform its behaviour e.g specify its return value)
Interaction testing: Monitoring the interactions of the system under test with the dummy class e.g monitor how many times a function is called without actually executing
Faking: Creating a simplified interface that is meant to mimic the actual dependency required but is used as the actual dependency may be slow, nondeterministic and slow down build times, when used in a unit test
Patterns that enable the use of testing doubles
Dependency Injection: Constructors accept dependencies when instantiating a class. Allows testing doubles to be passed as a dependency. Boilerplate from this can be reduced by dependency injection frameworks
Good Testing Practices
Use the real implementation(classical testing) of dependencies if possible (not too slow and affect build performance). This will be the most efficient at catching bugs
Try to use fakes if classical testing is not possible and many tests rely on this for tests.
Use State testing (Running a function and checking its state) over interaction testing, as interaction testing does not verify that the function executed correctly.
Use interaction testing for state-changing functions since non-state changing functions can have its return value asserted. The exception is if the non-state changing function takes its value from outside the system.
Avoid overspecifying interaction tests, if arguments are not related to the test, do not include it in the verification e.g verify(testCall).someFunc("impt variable", any(), any())
Pros of using testing doubles
Good a system that is slow, unpredictable and complex. Using test doubles makes tests less flakey (random errors occuring), allowing devs to trust the test results. Also allowing the problem to be isolated
Cons of using testing doubles
Over use of testing doubles such as stubbing and interaction testing can cause tests to be brittle. Small changes to the underlying API cause widespread breaking of tests which have to be fixed, reducing developer efficiency
Book: SE@Google Chapter: 18 - Test Doubles
Summary:
Main Takeaway If possible, classical testing is the ideal testing strategy. Testing doubles simplify and allow systems to be tested in isolation when classical testing is slow, unpredictable or complex. Though unit tests using test doubles can be easy to write, the inappropriate use of testing strategies will reduce developer productivity and not be efficient at catching bugs.
Overview
Testing Principles Testability: Ease of writing unit tests for code e.g ease of using test doubles (Should be considered when writing code - else refactors have to be made later)
Applicability: Inappropriate use of test doubles may reduce engineering productivity, using the real implementation is sometimes the best option
Fidelity: The closeness which the test double mimics the real implementation. No value if the test double has low fidelity. Larger scope tests need to supplement unit tests which use test doubles.
Testing Double Techniques
Patterns that enable the use of testing doubles
Good Testing Practices
verify(testCall).someFunc("impt variable", any(), any())
Pros of using testing doubles
Cons of using testing doubles