CSCI 370 Lec 21: Writing Testable Code
Key Concepts
Section titled “Key Concepts”Characteristics of Testable Software
Section titled “Characteristics of Testable Software”- Methods Should Return a Value: This allows the results to be tested.
- Predictable Results: Avoid using elements like random number generators that make testing difficult.
- Environment Independence: Software should not rely on specific environments. For example, self-driving car software can be tested without an actual car.
Seams in Code
Section titled “Seams in Code”A seam is a break in the code that allows testing of specific parts. Without seams, testing becomes difficult. For example:
Code Without Seams
Section titled “Code Without Seams”class Business { Business(int i) { i = i / 0; // Logic directly in the constructor }}Code With Seams
Section titled “Code With Seams”class Business { Business(int i) { i = handleI(i); // Logic moved to a separate method }
int handleI(int i) { return i / 0; }}By moving logic to a separate method, the code becomes testable.
Dependency Injection
Section titled “Dependency Injection”Dependency injection allows control over the dependencies of a class, making it easier to test. For example:
Using Real Dependencies
Section titled “Using Real Dependencies”Portfolio portfolio = new Portfolio(new RealStockMarket());portfolio.valueOfPortfolio();Using Mock Dependencies for Testing
Section titled “Using Mock Dependencies for Testing”Portfolio portfolio = new Portfolio(new MockStockMarket());portfolio.valueOfPortfolio();Mock objects allow predictable and controlled testing environments.
Law of Demeter
Section titled “Law of Demeter”The Law of Demeter suggests that methods should only use the properties or methods of the objects passed to them directly, not their “grandchild” objects. For example:
Violating the Law of Demeter
Section titled “Violating the Law of Demeter”void process(A a) { a.getB().getC().doSomething();}Following the Law of Demeter
Section titled “Following the Law of Demeter”void process(C c) { c.doSomething();}This makes the code easier to understand and test.
Idempotency in Unit Tests
Section titled “Idempotency in Unit Tests”Unit tests should be idempotent, meaning they should always produce the same result regardless of how many times they are run or in what order. For example:
- Uploading a picture should behave the same way every time.
Isolating Side Effects
Section titled “Isolating Side Effects”Side effects, such as writing to a file or relying on global variables, should be isolated into separate methods. This ensures the core logic can be tested independently. For example:
Code With Side Effects
Section titled “Code With Side Effects”void countWords(String inputFile, String outputFile) { // Reads from inputFile and writes to outputFile}Code With Isolated Side Effects
Section titled “Code With Isolated Side Effects”int countWords(InputStream input) { // Core logic for counting words}
void writeResult(OutputStream output, int wordCount) { // Handles writing the result}}By isolating side effects, the countWords method can be tested independently using mock streams.
Images
Section titled “Images”

Summary
Section titled “Summary”- Use seams to make code testable.
- Apply dependency injection to control dependencies.
- Follow the Law of Demeter to simplify code and testing.
- Ensure unit tests are idempotent.
- Isolate side effects to separate methods for better testability.