Skip to content

feat: implement JUnit5 reporter with Java/Kotlin POJO support and CI/CD#80

Open
jsbattig wants to merge 3 commits into
nizos:mainfrom
LightspeedDMS:feature/junit5-reporter
Open

feat: implement JUnit5 reporter with Java/Kotlin POJO support and CI/CD#80
jsbattig wants to merge 3 commits into
nizos:mainfrom
LightspeedDMS:feature/junit5-reporter

Conversation

@jsbattig

@jsbattig jsbattig commented Nov 8, 2025

Copy link
Copy Markdown

Extends TDD Guard to Java ecosystem to enable AI-driven TDD enforcement for Java/Kotlin projects. Discovered during implementation that strict incremental TDD rules conflict with compiled languages' compilation phase, requiring language-specific adaptations.

WHY This Change:

  • Java is a major language lacking TDD Guard support
  • Claude Code needs TDD enforcement for Java projects
  • Compiled languages require different TDD workflow than interpreted languages
  • Educational feedback improves AI agent's Java development patterns
  • Automated releases reduce manual maintenance burden

Core Features:

  • JUnit5 Platform TestExecutionListener with service provider auto-discovery
  • Educational feedback: 5 pattern detectors for AI agent learning
  • Self-detection: Zero overhead when TDD Guard not present
  • Complete test type support: Standard, @nested, @ParameterizedTest, dynamic
  • Standard test.json format for consistency across all reporters

Java/Kotlin POJO Support (WHY NEEDED):
Problem: Strict incremental TDD blocks compilation phase stubs in Java. Tests won't compile without class/method stubs, but TDD Guard blocked stubs without test.json (which only exists after successful compilation).

Solution: Research-backed rules from Kent Beck's "Obvious Implementation":

  • Detects when test file exists (proves test-first intent)
  • Allows complete POJO structures (fields + constructor + getters) as one unit
  • Still blocks all business logic without test evidence
  • Zero impact on other languages (isolated to Java/Kotlin only)

Rationale: Java POJOs are structural code (data holders), not behavioral. Community consensus: Testing simple getters/setters is wasteful, they're covered by integration tests. Kent Beck: "If you know what to type and can do it quickly, then do it."

Distribution WHY:

  • GitHub Actions: Automated releases reduce manual errors and save time
  • Public JAR downloads: No authentication required, easier adoption
  • GitHub Packages: Option for users who prefer dependency management
  • Maven support: Extends reach beyond Gradle users

Boy Scout Rule WHY:
Fixed golangci-lint compatibility because tests were failing. Updated for v1.64+ (current version) to ensure CI/CD works properly. This benefits all contributors, not just this PR.

Testing WHY:

  • 193 tests ensure reporter works correctly across all JUnit5 test types
  • Manual integration validates real-world usage
  • Test fixtures excluded to prevent false failures in CI/CD

Implementation Decisions:

  • Service provider: Auto-discovery, no manual configuration needed
  • Thread-safe collector: Supports parallel test execution
  • Fallback hierarchy: Works in various project structures
  • Performance target: <1ms per test to minimize impact

Stories Completed: #2, #3, #4, #5, #6, #7, #10 (7 of 9)
Story #8 Skipped: Multi-Release JAR complexity not justified for test reporter
Story #9 Remaining: Comprehensive documentation

Extends TDD Guard to Java ecosystem to enable AI-driven TDD enforcement for
Java/Kotlin projects. Discovered during implementation that strict incremental
TDD rules conflict with compiled languages' compilation phase, requiring
language-specific adaptations.

WHY This Change:
- Java is a major language lacking TDD Guard support
- Claude Code needs TDD enforcement for Java projects
- Compiled languages require different TDD workflow than interpreted languages
- Educational feedback improves AI agent's Java development patterns
- Automated releases reduce manual maintenance burden

Core Features:
- JUnit5 Platform TestExecutionListener with service provider auto-discovery
- Educational feedback: 5 pattern detectors for AI agent learning
- Self-detection: Zero overhead when TDD Guard not present
- Complete test type support: Standard, @nested, @ParameterizedTest, dynamic
- Standard test.json format for consistency across all reporters

Java/Kotlin POJO Support (WHY NEEDED):
Problem: Strict incremental TDD blocks compilation phase stubs in Java. Tests
won't compile without class/method stubs, but TDD Guard blocked stubs without
test.json (which only exists after successful compilation).

Solution: Research-backed rules from Kent Beck's "Obvious Implementation":
- Detects when test file exists (proves test-first intent)
- Allows complete POJO structures (fields + constructor + getters) as one unit
- Still blocks all business logic without test evidence
- Zero impact on other languages (isolated to Java/Kotlin only)

Rationale: Java POJOs are structural code (data holders), not behavioral.
Community consensus: Testing simple getters/setters is wasteful, they're
covered by integration tests. Kent Beck: "If you know what to type and can
do it quickly, then do it."

Distribution WHY:
- GitHub Actions: Automated releases reduce manual errors and save time
- Public JAR downloads: No authentication required, easier adoption
- GitHub Packages: Option for users who prefer dependency management
- Maven support: Extends reach beyond Gradle users

Boy Scout Rule WHY:
Fixed golangci-lint compatibility because tests were failing. Updated for
v1.64+ (current version) to ensure CI/CD works properly. This benefits all
contributors, not just this PR.

Testing WHY:
- 193 tests ensure reporter works correctly across all JUnit5 test types
- Manual integration validates real-world usage
- Test fixtures excluded to prevent false failures in CI/CD

Implementation Decisions:
- Service provider: Auto-discovery, no manual configuration needed
- Thread-safe collector: Supports parallel test execution
- Fallback hierarchy: Works in various project structures
- Performance target: <1ms per test to minimize impact

Stories Completed: #2, #3, #4, #5, #6, #7, #10 (7 of 9)
Story #8 Skipped: Multi-Release JAR complexity not justified for test reporter
Story #9 Remaining: Comprehensive documentation

Co-Authored-By: Claude <noreply@anthropic.com>
@jsbattig jsbattig mentioned this pull request Nov 8, 2025
@nizos

nizos commented Nov 10, 2025

Copy link
Copy Markdown
Owner

Hi @jsbattig!

Thank you for this contribution! Java support is something that a lot of people have asked for!

I need some time to review this properly as my time is currently spread thin. I'll review it within the next day or two.

Thanks again for taking this on!

jsbattig and others added 2 commits November 10, 2025 22:17
…cken-and-egg problems

Addresses GitHub Issue #12 by expanding compiled language rules from "Pure POJOs"
to comprehensive "Structural Constructs" covering all zero-logic patterns that
must exist before tests can compile.

Changes:
- Renamed "Pure POJO Definition" to "Structural Constructs Definition"
- Added support for 9 construct types: enums, records, data classes, interfaces,
  exception classes, constants, companion objects, sealed types, annotations
- Provided ALLOWED vs BLOCKED examples for each construct type
- Added comprehensive test coverage (13 tests, all passing)
- Updated decision tree to list all supported structural constructs
- Added .code-indexer/ to .gitignore

Impact:
- Enables proper TDD workflow for Java/Kotlin structural types
- Solves chicken-and-egg problem where tests can't compile without types,
  but TDD Guard blocks creating types without test output
- Maintains safety by still blocking constructs with business logic

Test Results:
- Unit tests: 13/13 passing
- Manual E2E validation: Confirmed enum/record/interface creation works
- All constructs properly allowed when test file exists

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement configurable source directory resolution for JUnit5 reporter
to support projects with non-standard layouts. Previously hardcoded
paths (src/test/java, src/main/java) now dynamically resolved via
configuration.

Key Changes:
- Add SourceDirectoryResolver with system property/env var support
- Update TestResultCollector to use dynamic paths with file checking
- Convert SourceAnalyzer to instance-based with configurable dirs
- Integrate SourceDirectoryResolver throughout pattern detectors
- Add comprehensive test coverage (212 tests, all passing)
- Update documentation with Gradle/Maven configuration examples

Configuration:
- System properties: tddguard.testSourceDirs, tddguard.mainSourceDirs
- Environment variables: TDDGUARD_TEST_SOURCE_DIRS, etc
- Resolution: System properties → Env vars → Defaults
- Backward compatible with standard Maven/Gradle layouts

Testing:
- 13 new SourceDirectoryResolver tests
- 18 new integration tests for dynamic path resolution
- 4 manual E2E scenarios validated
- Zero regressions in existing functionality

Bug Fixes:
- Fix TestResultCollector file path resolution bug
- Now checks file existence in each configured directory
- Previously always used first directory incorrectly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@jsbattig

Copy link
Copy Markdown
Author

@nizos , no problem, thanks you for spending the time building this amazing tool!
I added a couple of commits recently, we have one of our architects here test-driving it and he found a couple of situations that needed more flexibility.

@jordyvanvorselen

Copy link
Copy Markdown

Any rough idea when this going to be merged? @nizos

@afloresescarcega

Copy link
Copy Markdown

Was able to validate pretooluse hook worked even today. Hope this PR could make it in! 🙏

@nizos

nizos commented Mar 1, 2026

Copy link
Copy Markdown
Owner

Hi @jsbattig @jordyvanvorselen and @afloresescarcega,

I'm sorry for the delay on this review. I have now gone through the full PR in detail.

The reporter core is well structured and the effort and thought that went into this is clear and really appreciated. The challenges you identified with compiled languages are also real. That said, I have concerns about scope and architectural fit that I think we need to work through before this can be merged.

Compilation fail handling

The core src/ changes are designed to solve the problem of Java not producing test.json when compilation fails. This is a real problem, but we already have an established solution for it. Both Go and Rust handle compilation failures by detecting build errors and writing a synthetic "compilation" test module with state: "failed" to test.json. The existing schema supports this. The validator then sees a failing test and can make informed decisions without special language rules.

If the JUnit5 reporter follows the same pattern, detecting javac/Gradle compilation errors and reporting them as failed tests, the core validation layer doesn't need to change. Reporters handle language-specific concerns while the core remains largely language-agnostic.

Pattern detection and educational feedback

The pattern detection goes beyond what any existing reporter does. The current reporters strictly capture test results. Analysis and feedback happen in the validation layer. The closest equivalent we have is linter integration which consumes project-specific rules.

This is interesting work and definitely worth exploring further, but it's a separate feature that deserves its own discussion and PR. For the initial reporter, I'd suggest keeping it focused on test result capture.

Other items to separate out

  • golangci-lint fix: Legitimate fix, but unrelated to JUnit5. Would be great as a standalone PR.
  • File type detection expansion: The current detection exists to select the right test result parser (pytest has a different schema). It's not something the validator sees. We're looking to move away from this approach anyway. Generalising the parsing rather than branching on language. So expanding it to more languages likely isn't the direction we'd go.
  • CI/CD release workflow: I appreciate going beyond what's expected. We don't have reporter-specific release workflows yet. This could be a separate discussion.

Reporter specific notes

The reporter core itself is solid. A few things I noticed:

  • ProjectRootResolver duplicates the logic in TddGuardListener.resolveProjectRoot(), one should be removed.
  • SourceDirectoryResolver is constructed twice in the listener
  • Pattern detectors are constructed eagerly even when enabled = false
  • There's a duplicate META-INF/services file at the project root that looks like a leftover

I realize that this is a lot of feedback on work you've already invested significant effort in, and I'm sorry about that.
The core reporter is good and I'd like to get Java support landed.

Can we start with a focused JUnit5 reporter PR? Listener + collector + JSON writer + models + tests, following the Go/Rust pattern for compilation failure handling. No core src/ changes needed. We can then follow up with issue/discussions and PRs for other pieces.

There are also merge conflicts on the branch, but let's align on the approach first before tackling those.

How does that sound?

@Hiro-Chiba

Hiro-Chiba commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Hi @jsbattig, thanks for laying the groundwork here. With @nizos's guidance I will be picking up the JUnit5 reporter, taking inspiration from your design while aligning the output with the canonical test.json schema used by other reporters, and splitting the work into smaller PRs per the review feedback. You will be credited for the design reference in commits and PR descriptions. Happy to have you involved if you are still around.

Hiro-Chiba added a commit to Hiro-Chiba/tdd-guard that referenced this pull request Apr 12, 2026
Sets up the JUnit5 reporter directory following the minitest/rspec
reporter conventions:

- Gradle Kotlin DSL build with Java 17 toolchain and Maven publishing
- Canonical schema model classes (TestResult, TestModule, TestCase, TestError)
  matching src/contracts/schemas/reporterSchemas.ts
- ProjectRootResolver per ADR-009 (accepts absolute and relative paths)
  and ADR-010 (explicit > TDD_GUARD_PROJECT_ROOT > error; no silent cwd fallback)
- README skeleton

Tests not yet run locally pending Java 17 environment setup.
ProjectRootResolverTest covers 8 cases: missing config, explicit path
precedence, env var fallback, absolute and relative path handling, and
cwd-within-root validation.

Refs nizos#67, nizos#80. Design inspired by @jsbattig's listener architecture.
@jsbattig

jsbattig commented Apr 13, 2026 via email

Copy link
Copy Markdown
Author

Hiro-Chiba added a commit to Hiro-Chiba/tdd-guard that referenced this pull request Apr 15, 2026
Implements the result capture pipeline for the JUnit5 reporter:

- TddGuardListener registers via META-INF/services and writes results on
  test plan completion. Activates only when TDD_GUARD_PROJECT_ROOT is
  set so the jar is safe to include on a classpath without opting in.
- TestResultCollector groups outcomes by moduleId (fully qualified class
  name). Pure data layer with no JUnit Platform dependency so it is
  directly unit-testable.
- TestJsonWriter serializes to the canonical schema defined in
  src/contracts/schemas/reporterSchemas.ts with atomic temp-file write.
  No external JSON library dependency.

Tests: 11 cases for TestJsonWriter (passed/failed/skipped, escaping,
atomic write, nested directory creation) and 9 for TestResultCollector
(grouping, state extraction, throwable handling, insertion order).

Tests not yet run locally pending Java 17 environment setup.

Refs nizos#67, nizos#80.
nizos pushed a commit to Hiro-Chiba/tdd-guard that referenced this pull request Apr 18, 2026
Sets up the JUnit5 reporter directory following the minitest/rspec
reporter conventions:

- Gradle Kotlin DSL build with Java 17 toolchain and Maven publishing
- Canonical schema model classes (TestResult, TestModule, TestCase, TestError)
  matching src/contracts/schemas/reporterSchemas.ts
- ProjectRootResolver per ADR-009 (accepts absolute and relative paths)
  and ADR-010 (explicit > TDD_GUARD_PROJECT_ROOT > error; no silent cwd fallback)
- README skeleton

Tests not yet run locally pending Java 17 environment setup.
ProjectRootResolverTest covers 8 cases: missing config, explicit path
precedence, env var fallback, absolute and relative path handling, and
cwd-within-root validation.

Refs nizos#67, nizos#80. Design inspired by @jsbattig's listener architecture.
nizos pushed a commit to Hiro-Chiba/tdd-guard that referenced this pull request Apr 18, 2026
Implements the result capture pipeline for the JUnit5 reporter:

- TddGuardListener registers via META-INF/services and writes results on
  test plan completion. Activates only when TDD_GUARD_PROJECT_ROOT is
  set so the jar is safe to include on a classpath without opting in.
- TestResultCollector groups outcomes by moduleId (fully qualified class
  name). Pure data layer with no JUnit Platform dependency so it is
  directly unit-testable.
- TestJsonWriter serializes to the canonical schema defined in
  src/contracts/schemas/reporterSchemas.ts with atomic temp-file write.
  No external JSON library dependency.

Tests: 11 cases for TestJsonWriter (passed/failed/skipped, escaping,
atomic write, nested directory creation) and 9 for TestResultCollector
(grouping, state extraction, throwable handling, insertion order).

Tests not yet run locally pending Java 17 environment setup.

Refs nizos#67, nizos#80.
nizos pushed a commit to Hiro-Chiba/tdd-guard that referenced this pull request Apr 18, 2026
Sets up the JUnit5 reporter directory following the minitest/rspec
reporter conventions:

- Gradle Kotlin DSL build with Java 17 toolchain and Maven publishing
- Canonical schema model classes (TestResult, TestModule, TestCase, TestError)
  matching src/contracts/schemas/reporterSchemas.ts
- ProjectRootResolver per ADR-009 (accepts absolute and relative paths)
  and ADR-010 (explicit > TDD_GUARD_PROJECT_ROOT > error; no silent cwd fallback)
- README skeleton

Tests not yet run locally pending Java 17 environment setup.
ProjectRootResolverTest covers 8 cases: missing config, explicit path
precedence, env var fallback, absolute and relative path handling, and
cwd-within-root validation.

Refs nizos#67, nizos#80. Design inspired by @jsbattig's listener architecture.
nizos pushed a commit to Hiro-Chiba/tdd-guard that referenced this pull request Apr 18, 2026
Implements the result capture pipeline for the JUnit5 reporter:

- TddGuardListener registers via META-INF/services and writes results on
  test plan completion. Activates only when TDD_GUARD_PROJECT_ROOT is
  set so the jar is safe to include on a classpath without opting in.
- TestResultCollector groups outcomes by moduleId (fully qualified class
  name). Pure data layer with no JUnit Platform dependency so it is
  directly unit-testable.
- TestJsonWriter serializes to the canonical schema defined in
  src/contracts/schemas/reporterSchemas.ts with atomic temp-file write.
  No external JSON library dependency.

Tests: 11 cases for TestJsonWriter (passed/failed/skipped, escaping,
atomic write, nested directory creation) and 9 for TestResultCollector
(grouping, state extraction, throwable handling, insertion order).

Tests not yet run locally pending Java 17 environment setup.

Refs nizos#67, nizos#80.
nizos pushed a commit that referenced this pull request Apr 18, 2026
Sets up the JUnit5 reporter directory following the minitest/rspec
reporter conventions:

- Gradle Kotlin DSL build with Java 17 toolchain and Maven publishing
- Canonical schema model classes (TestResult, TestModule, TestCase, TestError)
  matching src/contracts/schemas/reporterSchemas.ts
- ProjectRootResolver per ADR-009 (accepts absolute and relative paths)
  and ADR-010 (explicit > TDD_GUARD_PROJECT_ROOT > error; no silent cwd fallback)
- README skeleton

Tests not yet run locally pending Java 17 environment setup.
ProjectRootResolverTest covers 8 cases: missing config, explicit path
precedence, env var fallback, absolute and relative path handling, and
cwd-within-root validation.

Refs #67, #80. Design inspired by @jsbattig's listener architecture.
nizos pushed a commit that referenced this pull request Apr 18, 2026
Implements the result capture pipeline for the JUnit5 reporter:

- TddGuardListener registers via META-INF/services and writes results on
  test plan completion. Activates only when TDD_GUARD_PROJECT_ROOT is
  set so the jar is safe to include on a classpath without opting in.
- TestResultCollector groups outcomes by moduleId (fully qualified class
  name). Pure data layer with no JUnit Platform dependency so it is
  directly unit-testable.
- TestJsonWriter serializes to the canonical schema defined in
  src/contracts/schemas/reporterSchemas.ts with atomic temp-file write.
  No external JSON library dependency.

Tests: 11 cases for TestJsonWriter (passed/failed/skipped, escaping,
atomic write, nested directory creation) and 9 for TestResultCollector
(grouping, state extraction, throwable handling, insertion order).

Tests not yet run locally pending Java 17 environment setup.

Refs #67, #80.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants