← SEARCHABLE ARCHIVE
Java · 2026-08-12

Java 기초부터 마스터까지 — 10화

계층과 테스트로 유지보수 가능한 앱 만들기

INTERACTIVE READING MODE →

본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.

프로그램이 커지면 클래스 수보다 변경이 퍼지는 범위가 문제가 된다. 규칙, 저장소, 화면 경계를 나누고 동작을 테스트할 수 있게 만드는 것으로 Java 연재를 정리해보자.

계층의 책임

public final class OrderService {
    private final ItemRepository repository;

    public OrderService(ItemRepository repository) {
        this.repository = repository;
    }

    public OrderResult buy(int itemId, int gold) {
        Item item = repository.find(itemId)
            .orElseThrow(() -> new ItemNotFoundException(itemId));

        if (gold < item.getPrice()) {
            return OrderResult.notEnoughGold();
        }

        return OrderResult.success(item, gold - item.getPrice());
    }
}

서비스는 구매 규칙을 담당하고 데이터베이스나 HTTP 세부 구현을 직접 알지 않는다. 컨트롤러나 화면은 입력을 서비스에 전달하고 결과를 표현한다.

의존성을 바깥에서 넣기

public interface ItemRepository {
    Optional<Item> find(int id);
}

ItemRepository repository = new FileItemRepository(path);
OrderService service = new OrderService(repository);

구체 저장소를 생성자 밖에서 선택하면 테스트에서는 메모리 구현으로 바꿀 수 있다. 인터페이스는 모든 클래스마다 만드는 장식이 아니라 외부 기술이 바뀌는 경계에 둔다.

동작 테스트하기

@Test
void purchaseKeepsRemainingGold() {
    ItemRepository repository = new MemoryItemRepository(
        new Item(10, "Potion", 30)
    );
    OrderService service = new OrderService(repository);

    OrderResult result = service.buy(10, 100);

    assertEquals(70, result.getRemainingGold());
}

테스트는 내부 메서드 호출 순서보다 외부에서 보이는 규칙을 확인한다. 구현을 정리해도 같은 입력에 같은 계약이 유지되는지 알 수 있다.

프로젝트 도구

Gradle이나 Maven은 컴파일, 테스트, 라이브러리 버전, 패키징을 반복 가능한 명령으로 만든다. 개발 환경에서만 실행되는 프로젝트가 되지 않도록 빌드 파일과 테스트를 저장소에 함께 둔다.

좋은 Java 구조는 계층이 많은 구조가 아니다. 상태를 바꾸는 규칙, 외부 자원, 동시 작업의 수명이 분명하고 작은 단위로 검증할 수 있는 구조다.