06 Test Code Patterns - skylerto/Software-Testing GitHub Wiki
Test Code Patterns
Testing and Inheritance
- Should you retest inherited methods?
- Can you reuse superclass tests for inherited and overridden methods?
- To what extent should you exercise interaction among methods of all super classes and of the subclass under test?
Inheritance
- In the early years people thought that inheritance will reduce the need for testing
- Claim 1: “If we have a well-tested super class, we can reuse its code (in subclasses, through inheritance) with confidence and without retesting inherited code”
- Claim 2: “A good-quality test suite used for a superclass will also be sufficient for a subclass”
- Both claims are wrong.
Inheritance-related bugs
- Missing Override
- A subclass omits to provide a specialized version of a superclass method
- Subclass objects will have to use the superclass version, which might not be appropriate
- E.g., method equals in Object tests for reference equality. In a given class, it might be right to override this behaviour
Inheritance-related bugs
- Direct access to superclass fields from the subclass code
- Changes to the superclass implementation can create subclass bugs
- Subclass bugs or side effects can cause failure in superclass methods
- If a superclass is changed, all subclasses need to be tested
- If a subclass is changed, superclass features used in the subclass must be retested
Testing of Inheritance
- Principle: inherited methods should be retested in the context of a subclass
- Example 1: if we change some method m in a superclass, we need to retest m inside all subclasses that inherit it
Inheritance-related bugs
- Square Peg in a Round Hole
- Design Problem
- A subclass is incorrectly located in a hierarchy
- Liskov Substitution Principle (LSP): Functions that use references to base classes must be able to use objects of derived classes without knowing it.
Effect of Inheritance on Testing?
- Does not reduce the volume of test cases
- Rather, number of interactions to be verified goes up at each level of the hierarchy
Polymorphic Server Test
- Consider all test cases that exercise polymorphic methods
- According to LSP, these should apply at every level of the inheritance hierarchy
- Expand each test case into a set of test cases, one for each polymorphic variation
Testing abstract classes
- Abstract classes cannot be instantiated
- However, they define an interface and behaviour (contracts) that implementing classes will have to adhere to
- We would like to test abstract classes for functional compliance
- Functional Compliance is a module's compliance with some documented or published functional specification
Functional vs. syntactic compliance
- The compiler can easily test that a class is syntactically compliant to an interface
- All methods in the interface have to be implemented with the correct signature
- Tougher to test functional compliance
- A class implementing the interface
java.util.List
may be implementingget(int index)
orisEmpty()
incorrectly
- A class implementing the interface
- Think LSP...
Abstract Test Pattern
- This pattern provides the following
- A way to build a test suite that can be reused across descendants
- A test suite that can be reused for future as- yet-unidentified descendants
- Especially useful for writers of APIs.
Abstract Test Rule 1
-Write an abstract test class for every interface and abstract class
- An abstract test should have test cases that cannot be overridden
- It should also have an abstract Factory Method for creating instances of the class to be tested.
Abstract Test Rule 2
-Write a concrete test class for every implementation of the interface (or abstract class)
- The concrete test class should extend the abstract test class and implement the factory method
Guideline
- Tests defining the functionality of the interface belong in the abstract test class
- Tests specific to an implementation belong in a concrete test class
- We can add more test cases to TestSuperSlowStatPak that are specific to its implementation
Crash Test Dummy
- Most software systems contain a large amount of error handling code
- Sometimes, it is quite hard to create the situation that will cause the error
- Example: Error creating a file because the file system is full
- Solution: Fake it!