Skip to content

CSCI 370 Lec 21: Writing Testable Code

  1. Methods Should Return a Value: This allows the results to be tested.
  2. Predictable Results: Avoid using elements like random number generators that make testing difficult.
  3. Environment Independence: Software should not rely on specific environments. For example, self-driving car software can be tested without an actual car.

A seam is a break in the code that allows testing of specific parts. Without seams, testing becomes difficult. For example:

class Business {
Business(int i) {
i = i / 0; // Logic directly in the constructor
}
}
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 allows control over the dependencies of a class, making it easier to test. For example:

Portfolio portfolio = new Portfolio(new RealStockMarket());
portfolio.valueOfPortfolio();
Portfolio portfolio = new Portfolio(new MockStockMarket());
portfolio.valueOfPortfolio();

Mock objects allow predictable and controlled testing environments.

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:

void process(A a) {
a.getB().getC().doSomething();
}
void process(C c) {
c.doSomething();
}

This makes the code easier to understand and test.

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.

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:

void countWords(String inputFile, String outputFile) {
// Reads from inputFile and writes to outputFile
}
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.

Image 1 Image 2 Image 3 Image 4 Image 5 Image 6

  • 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.