Java Omnibus
OOAD & Architecture / OOA / OOD process / TDD and Design Emergence in Java
OOA/OOD Process Java Order Management
OOA / OOD Process

TDD helps design emerge when tests force you to make responsibilities and collaborations explicit.

TDD is often taught as a correctness technique, but it is also a design technique. When used well, tests expose whether responsibilities are too entangled, whether a class is too broad, and whether collaboration boundaries are awkward. That is why TDD fits naturally into OOAD process discussions.

Why This Topic Matters

Why it matters in real Java systems

In Java systems with payment, inventory, and shipment integrations, tests quickly reveal whether orchestration is overly coupled to low-level details. A design that is painful to test is often painful to evolve for the same reasons.

Core Explanation

The design idea behind this process step

TDD supports design emergence by creating quick feedback about object shape. When you write a test for a use case, you have to choose what to construct, what to stub, and what outcome to assert. Those choices often reveal whether the design is carrying too much hidden coupling or too little domain meaning.

For Java teams, this can be a strong bridge from OOAD theory into day-to-day coding. A good use case with focused collaborators is usually easier to test. A God service usually is not.

TDD should complement, not replace, analysis and modeling. It refines design pressure close to the code, while OOAD gives the wider shape.

Concept Breakdown

Key ideas to keep in view

Concept

Tests reveal dependency pain

If a simple use case needs excessive setup, the responsibility boundaries may be wrong.

Concept

Small tests encourage focused objects

Objects with clear ownership are easier to exercise without large scaffolding.

Concept

Emergence still needs direction

TDD helps, but it does not replace domain understanding or analysis.

Java Example

Java example in the Order Management domain

Java example: a testable use case with focused dependencies
package org.javaomnibus.ecommerce.process;

public final class MarkOrderShippedUseCase {
    private final ShipmentRepository shipmentRepository;
    private final NotificationPublisher notificationPublisher;

    public MarkOrderShippedUseCase(
        ShipmentRepository shipmentRepository,
        NotificationPublisher notificationPublisher
    ) {
        this.shipmentRepository = shipmentRepository;
        this.notificationPublisher = notificationPublisher;
    }

    public void handle(ShipmentId shipmentId) {
        Shipment shipment = shipmentRepository.requireById(shipmentId);
        shipment.markShipped();
        shipmentRepository.save(shipment);
        notificationPublisher.publishShipmentSent(shipment);
    }
}
Code Walkthrough

Step by step

  1. The use case is easy to test because its dependencies are explicit and focused.
  2. Shipment still owns its own state transition, preserving domain meaning.
  3. A test can validate collaboration and outcome without needing a full environment.
  4. That ease of testing is often a sign the design boundaries are healthy.
Real-World Usage

Where this process step shows up in practice

  • Improving design incrementally in feature work rather than through separate refactoring projects
  • Teaching teams that testability and design quality are closely related
  • Catching over-coupled orchestration early while a feature is still small
Trade-Offs

When to be careful

TDD alone cannot discover a good domain model if the team has not understood the domain. It sharpens local design, but it still needs a strong problem understanding to guide it.

Common Mistakes

Frequent mistakes to watch for

  • Writing tests only against controllers or frameworks and calling that design validation
  • Using mocks to prop up an over-coupled design rather than simplifying it
  • Expecting emergence to replace deliberate responsibility assignment
  • Confusing test quantity with design quality
Related Pages

Related concepts


What To Read Next