PromleeBlog
sitemap
aboutMe

posting thumbnail
도메인 중심으로 TDD 적용하기 - Spring Boot로 시작하는 TDD 6편
Applying Domain-Centric TDD - TDD with Spring Boot Part 6

📅

🚀

들어가기 전에 🔗

지난 시간까지 우리는 리팩토링을 통해 코드를 깔끔하게 분리했습니다.
ProductService와 ProductRepository가 각각 자신의 역할을 하도록 만들었죠.
하지만 아직 이 클래스들은 순수한 자바 객체일 뿐, 스프링이 관리하는
빈(Bean)
은 아닙니다.

이번 시간에는 이 코드들을 스프링 환경에 맞게 등록하고, 왜 우리가
Controller(웹 계층)
보다
Service(도메인 계층)
를 먼저 테스트하고 개발해야 하는지 그 이유를 알아보겠습니다.

🚀

스프링 빈으로 등록하기 🔗

우리가 만든 클래스들이 스프링 부트 애플리케이션 안에서 동작하려면 스프링에게 "이 녀석들을 관리해 줘"라고 알려줘야 합니다.
이때 사용하는 것이 바로 어노테이션입니다.

1. Repository 등록 🔗

먼저 저장소를 스프링 빈으로 등록합니다.
@Repository@Component를 붙여주면 됩니다.
src/main/java/com/example/tdd_study/product/ProductRepository.java
package com.example.tdd_study.product;
 
import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.Map;
 
@Repository
public class ProductRepository {
    private Map<Long, Product> persistence = new HashMap<>();
    private Long sequence = 0L;
 
    public void save(Product product) {
        product.assignId(++sequence);
        persistence.put(product.getId(), product);
    }
}

2. Service 등록 🔗

서비스 클래스에도 @Service 어노테이션을 붙여줍니다.
src/main/java/com/example/tdd_study/product/ProductService.java
package com.example.tdd_study.product;
 
import org.springframework.stereotype.Service;
 
@Service
public class ProductService {
    private final ProductRepository productRepository;
 
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
 
    public void register(String name, int price) {
        Product product = new Product(name, price);
        productRepository.save(product);
    }
}
자, 이제 스프링 빈으로 등록되었습니다.
여기서 질문이 생길 수 있습니다.
코드를 수정하고 어노테이션을 붙였는데, 기존 테스트 코드를 고쳐야 할까요?

정답은
"아니오"
입니다.
우리가 작성한 테스트(ProductServiceTest)는 순수한 자바 단위 테스트이기 때문에, 클래스에 어노테이션이 붙든 말든 상관없이 잘 돌아갑니다.
이것이 바로
POJO(Plain Old Java Object)
중심 개발의 장점입니다.

🚀

왜 Controller보다 Service를 먼저 만들까요? 🔗

많은 초보 개발자분이 개발을 시작할 때
Controller
부터 만듭니다.
웹 브라우저에서 URL을 호출하고 화면에 무언가 나오는 것을 빨리 보고 싶기 때문입니다.
하지만 TDD에서는 보통 안쪽(도메인)에서 바깥쪽(API)으로 개발하는 것을 권장합니다.
이유는 다음과 같습니다.

🚀

비즈니스 로직 검증 추가하기 🔗

도메인 중심 개발이란, 단순히 데이터를 옮기는 것이 아니라
업무 규칙
을 코드로 구현하는 것입니다.
새로운 요구사항을 하나 추가해 보겠습니다.
상품 가격 정책: 상품 가격은 1000원 단위로만 설정할 수 있다. (예: 4500원 불가능, 4000원 가능)

1. 실패하는 테스트 작성 (Red) 🔗

Product 클래스에 대한 테스트를 먼저 작성합니다.
Service가 아닌 도메인 객체(Product) 자체를 테스트하는 것입니다.
src/test/java/com/example/tdd_study/product/ProductTest.java
package com.example.tdd_study.product;
 
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
class ProductTest {
 
    @Test
    void 상품_가격은_1000원_단위여야_한다() {
        assertThatThrownBy(() -> new Product("상품", 1500))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("1000원 단위");
    }
}

2. 기능 구현 (Green) 🔗

테스트를 통과시키기 위해 Product 클래스의 생성자에 검증 로직을 추가합니다.
src/main/java/com/example/tdd_study/product/Product.java
// ... 기존 import 생략
@Getter
public class Product {
    // ... 필드 생략
 
    public Product(String name, int price) {
        Assert.hasText(name, "상품명은 필수입니다.");
        Assert.isTrue(price > 0, "상품 가격은 0보다 커야 합니다.");
        // 추가된 로직
        Assert.isTrue(price % 1000 == 0, "상품 가격은 1000원 단위여야 합니다.");
        
        this.name = name;
        this.price = price;
    }
    // ... 메서드 생략
}
이렇게 도메인 객체 내부에 로직을 넣으면, ProductService는 가격 정책을 몰라도 됩니다.
그저 Product를 생성하기만 하면 알아서 검증이 되기 때문입니다.
이것이
도메인 중심 설계
의 시작입니다.

🚀

도메인 설계와 테스트의 관계 🔗

우리가 방금 한 것처럼 로직을 Service나 Controller가 아닌
Product(도메인 객체)
안으로 밀어 넣으면 테스트가 정말 쉬워집니다.


🚀

결론 🔗

오늘은 도메인 중심의 TDD 적용 방법에 대해 알아보았습니다.
핵심 내용은 다음과 같습니다.
이제 우리의 핵심 엔진은 완성되었습니다.
다음 시간에는 이 엔진을 외부와 연결하는 저장소,
Repository 계층의 테스트 전략
에 대해 자세히 알아보겠습니다.
가짜 메모리 저장소가 아닌, 진짜 JPA를 활용하는 방법도 맛볼 예정입니다.

참고 🔗