Install
openclaw skills install bookforge-test-harness-entry-diagnosticsDiagnose exactly why a class or method cannot be placed under test and route to the right dependency-breaking technique. Use whenever a developer says 'I can't instantiate this class in tests', 'the test harness won't compile', 'this class has too many constructor dependencies', 'the constructor connects to the database', 'can't test this private method', 'I need to sense what this method does', 'hidden singleton dependency'. Activates for 'test harness', 'class under test', 'constructor dependencies', 'irritating parameter', 'hidden dependency', 'construction blob', 'pass null', 'construction test', 'method not accessible', 'method side effects', 'can't get this class in a test harness', 'can't run this method in a test harness', 'singleton in constructor', 'include dependencies', 'onion parameter', 'aliased parameter'.
openclaw skills install bookforge-test-harness-entry-diagnosticsA systematic diagnostic for determining exactly why a class or method cannot be placed under test, and routing each specific obstacle to the correct dependency-breaking technique. Based on Feathers' Chapter 9 (class-level) and Chapter 10 (method-level) obstacle catalogs.
Use this skill when Step 3 of the Legacy Code Change Algorithm ("Break Dependencies") is blocked — when a developer cannot write a test because the class won't instantiate or the method can't be invoked. The diagnostic applies whether the problem surfaces as a compile error, a runtime exception, a dangerously slow test, or an inability to sense what the code actually did.
Do not use this skill when the class is already testable but the tests are too slow or fragile — that is a test quality problem, not a dependency-breaking problem.
Before running the diagnostic, collect:
If you have not yet attempted to instantiate the class in a test, do that first (Step 1 below). The compiler or runtime error message IS the diagnostic signal.
Before any analysis, write the simplest possible test: one that just calls the constructor with no assertions.
// Java / JUnit example
public void testCreate() {
MyClass obj = new MyClass();
}
// C++ / CppUnit example
TEST(create, MyClass) {
MyClass obj;
}
Why this first: The compiler tells you exactly what dependencies are missing. The runtime tells you exactly which side effects fire. You do not need to reason about the code — the toolchain performs the diagnosis for you. Construction tests look strange (no assertion, no verification), but they are free diagnostic instruments. Once you can construct the object, rename or replace the test with a real one.
Attempt to compile and run. The error message routes you directly to the root cause in Step 2.
Feathers identifies four root causes for class-level test resistance. Map your error to one:
| # | Root Cause | Typical Signal |
|---|---|---|
| 1 | Objects cannot be created easily | Too many hard-to-build parameters; nested object chains |
| 2 | Test harness won't build with the class in it | Compile errors from header chains (C++) or linker failures |
| 3 | Constructor has bad side effects | Test connects to DB, sends email, writes files, hits network |
| 4 | Significant work in constructor; need to sense it | Heavy initialization; values computed during construction are needed in assertions |
Root causes 1, 6, and 7 (Onion Parameter, Aliased Parameter) → hard-to-create objects Root cause 2 → build/link failure (Horrible Include Dependencies, C++ only) Root causes 3 and 4 → constructor side effects or resource allocation (Hidden Dependency, Construction Blob) Root cause 4 (sensing) → Construction Blob with sensing need
Once you have the root cause, identify the specific case using these detection rules:
Case 1 — Irritating Parameter
Case 2 — Hidden Dependency
new SomeService() or SomeGlobal.connect() internally — the dependency is not in the parameter listnew call outside the constructor and pass the object in. Provide a convenience constructor with the original signature that delegates to the new one.Case 3 — Construction Blob
Case 4 — Irritating Global Dependency (Singleton)
SomeClass.getInstance() or accesses a static global; cannot be replaced from outsidesetTestingInstance(T instance) method to the singleton that replaces the static field. In tests, set a fake before each test and reset after.Case 5 — Horrible Include Dependencies (C++ only)
Fakes.h include file, creating a separate test binary for this classCase 6 — Onion Parameter
Case 7 — Aliased Parameter
Full case-to-technique table: See references/class-level-cases.md.
If the class IS instantiable but a specific method cannot be exercised in a test, move to Chapter 10 diagnostics. These apply after Step 3 resolves construction obstacles.
Obstacle 1 — Method Not Accessible
private, package-private, or otherwise hidden; you cannot call it from the test fileprivate to protected and create a test subclass that exposes it publicly. This is preferable to reflection-based access, which masks design problems.Obstacle 2 — Hard to Construct Parameters
Obstacle 3 — Method Has Bad Side Effects
Obstacle 4 — Need to Sense Effects Through an Object
Full obstacle-to-technique table: See references/method-level-cases.md.
After completing the classification, write a short diagnostic report (template in Outputs section) naming:
When multiple obstacles exist, resolve them in this order:
| Input | Required | Description |
|---|---|---|
| Class name | Yes | Fully qualified name of the class under investigation |
| Language | Yes | Java, C#, C++, Python, etc. |
| Constructor signature | Yes | Parameter types and their dependencies |
| Error message | Recommended | Compile error, linker error, or runtime exception from the construction test |
| Test framework | Yes | JUnit, NUnit, Google Test, pytest, etc. |
| Target method | If method-level | The specific method you want to test |
diagnostic-report.md)## Test Harness Diagnostic: [ClassName]
**Language:** [Java / C# / C++ / other]
**Framework:** [JUnit / NUnit / Google Test / other]
**Date:** [ISO date]
### Construction Test Result
[Paste compile error or runtime exception, or "compiled and ran cleanly"]
### Root Cause
[One of: Objects can't be created easily / Harness won't build / Constructor has bad side effects / Significant work in constructor]
### Specific Case
[One of the 7 class-level cases or 4 method-level obstacles — use the case name]
### Detection Evidence
[The specific line/error/behavior that triggered this classification]
### Recommended Technique
[Technique name (Part III reference page if known)]
### Ordered Sequence (if multiple obstacles)
1. [First technique — addresses build/compile blocker]
2. [Second technique — addresses construction blocker]
3. [Third technique — addresses method-level blocker]
### Rationale
[One sentence per technique explaining why it fits this specific situation]
Construction tests are free diagnostic tools — use them first. Writing a testCreate() with no assertion costs almost nothing and eliminates all guesswork about which dependencies are actually needed.
The error message IS the diagnosis — read it. The compiler or runtime enumerates missing dependencies precisely. Treat it as a structured input to the classification process, not as noise to dismiss.
Pass Null is legitimate when the parameter is unused in the test path. In garbage-collected languages, null for an unused parameter is safe — the runtime will surface misuse immediately as a NullPointerException. Do not pass null in production code. In C/C++, do not pass null unless the runtime detects null pointer dereferences.
When multiple obstacles exist, order matters. Fix construction obstacles before method-level obstacles. Fix compile/link obstacles before construction obstacles. Attempting to sense through a method while the class still won't compile is wasted effort.
The goal is testability, not elegance. Test subclasses and methods with poor names are acceptable intermediate states. Once tests exist, the design can be improved safely.
Situation: CreditValidator(RGHConnection connection, CreditMaster master, String id) — RGHConnection opens a TCP socket when constructed.
Construction test result: Compiles, but the test takes 10 seconds and fails when the server is down.
Classification: Root Cause 1 (objects can't be created easily) → Case 1 (Irritating Parameter)
Recommended technique: Extract Interface on RGHConnection → create IRGHConnection. Implement FakeConnection returning pre-configured reports. CreditMaster loads a file quickly — it is not the problem.
Pass Null alternative: If the specific method being tested does not call any RGHConnection methods, pass null for connection and confirm with the construction test.
Situation: PaymentProcessor() constructor calls new DatabaseFactory(config).connect() internally. No parameters.
Construction test result: Runs, but hits the database, which is unavailable in CI.
Classification: Root Cause 3 (constructor has bad side effects) → Case 2 (Hidden Dependency)
Recommended technique: Parameterize Constructor — extract the DatabaseFactory creation out of the constructor. Add PaymentProcessor(DatabaseFactory factory) as the primary constructor. Provide the original no-arg constructor delegating to it: PaymentProcessor() { this(new DatabaseFactory(config)); }. In tests, pass a fake DatabaseFactory.
Situation: WatercolorPane(Form border, WashBrush brush, Pattern backdrop) constructor creates Panel, Panel, and FocusWidget internally in sequence. You need to assert on the state of FocusWidget after construction.
Classification: Root Cause 4 (significant work in constructor, needs sensing) → Case 3 (Construction Blob)
Recommended technique (Java/C#): Extract and Override Factory Method — extract the FocusWidget creation into protected FocusWidget createCursor(WashBrush brush, Panel panel), override it in a test subclass to return a TestingFocusWidget.
Fallback (C++): Supersede Instance Variable — add void supersedeCursor(FocusWidget* newCursor), call it in the test after construction. Delete the old cursor carefully.
references/class-level-cases.md — Full 7-case reference table: detection rules, techniques, worked examples, trade-offsreferences/method-level-cases.md — Full 4-obstacle reference table: detection rules, techniques, worked examplesSource chapters:
Part III techniques referenced:
CC-BY-SA 4.0 — BookForge Skills. Source: Working Effectively with Legacy Code by Michael C. Feathers (2004, Prentice Hall). Skills represent transformative synthesis; book content is not reproduced verbatim.
Prerequisites (run before this skill):
legacy-code-change-algorithm — this diagnostic executes Step 3 of that algorithmseam-type-selector — identifies the seam type (object, link, preprocessing) to exploit; use when the technique recommendation requires seam selectionDownstream (run after this skill):
dependency-breaking-technique-executor — executes the specific technique identified by this diagnostic