개발 공부/백엔드

제미나이와 게시판 만들기: (3) 서비스 계층 구현과 테스트

baby-t 2025. 10. 4. 10:28

지난 포스팅에서는 JPA를 사용하여 데이터베이스와 통신하는 리포지토리(Repository) 계층까지 완성했습니다.

이번 포스팅에서는 리포지토리를 활용하여 핵심 비즈니스 로직을 처리하는 서비스(Service) 계층을 구현하고, 작성한 코드가 올바르게 동작하는지 검증하는 테스트 코드 작성법에 대해 알아보겠습니다.

### 1. 서비스 계층(Service Layer) 구현

서비스 계층은 컨트롤러로부터 전달받은 데이터를 가지고, 실제 업무 규칙에 따라 데이터를 가공하고 처리하는 역할을 합니다. UserService를 예시로 살펴보겠습니다.

UserService.java

JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 이루어져야 하므로, 클래스 레벨에 @Transactional을 붙여줍니다.

Java
 
package com.example.demo.service;

import com.example.demo.domain.User;
import com.example.demo.repository.UserRepository;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;

@Transactional
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * 회원 가입
     */
    public Long join(User user) {
        // 비즈니스 로직 1: 중복 아이디는 허용하지 않음
        validateDuplicateUser(user);
        userRepository.save(user);
        return user.getId();
    }

    private void validateDuplicateUser(User user) {
        userRepository.findByUsername(user.getUsername())
                .ifPresent(u -> {
                    throw new IllegalStateException("이미 존재하는 아이디입니다.");
                });
    }

    /**
     * 로그인
     */
    public Optional<User> login(String username, String password) {
        // 비즈니스 로직 2: 아이디와 비밀번호가 일치하는지 확인
        return userRepository.findByUsername(username)
                .filter(user -> user.getPassword().equals(password));
    }
}

 

그 외에도 post와 comment 서비스도 작성해줍니다.

### 2. 테스트 코드 작성: 왜, 그리고 어떻게? 🧪

코드를 작성하는 것만큼이나 중요한 것이 바로 '테스트'입니다. 테스트 코드는 내 코드의 안정성을 보장하는 안전망이자, 다른 개발자에게 코드의 사용법을 알려주는 설명서가 됩니다. 오늘은 두 가지 종류의 테스트를 진행했습니다.

#### 단위 테스트 (Unit Test): 서비스 로직 검증

단위 테스트는 스프링이나 DB의 도움 없이, 순수 자바 코드로 클래스 하나의 로직을 검증하는 테스트입니다. 매우 빠르고 간단하게 비즈니스 로직의 정합성을 확인할 수 있습니다.

  • 목표: UserService의 '중복 회원 가입 방지' 로직이 잘 동작하는지 검증
  • 방법: 실제 DB 대신 가짜 MemoryMemberRepository를 사용하여 테스트

UserServiceTest.java

Java
 
class UserServiceTest {

    UserService userService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        userService = new UserService(memberRepository);
    }

    @Test
    void 중복_회원_예외_테스트() {
        // given
        User user1 = new User();
        user1.setUsername("spring");
        User user2 = new User();
        user2.setUsername("spring");

        // when
        userService.join(user1);

        // then: user2를 가입시킬 때 IllegalStateException이 발생하는 것이 성공!
        assertThrows(IllegalStateException.class, () -> userService.join(user2));
    }
}

#### 통합 테스트 (Integration Test): DB 연동 검증

통합 테스트는 실제 스프링 컨테이너와 데이터베이스를 연동하여 전체적인 흐름을 검증하는 테스트입니다.

  • 목표: JpaUserRepository가 실제 DB(H2)와 통신하며 데이터를 잘 저장하는지 검증
  • 방법: @SpringBootTest와 @Transactional을 사용하여 테스트

JpaUserRepositoryTest.java

Java
 
@SpringBootTest
@Transactional // 테스트 후 DB 롤백
class JpaUserRepositoryTest {

    @Autowired UserRepository userRepository;

    @Test
    @Rollback(false) // 테스트 데이터를 DB에서 직접 확인하고 싶을 때 사용
    void 저장_테스트() {
        // given
        User user = new User();
        user.setUsername("testuser");

        // when
        userRepository.save(user);

        // then
        User foundUser = userRepository.findByUsername("testuser").get();
        assertThat(foundUser.getUsername()).isEqualTo(user.getUsername());
    }
}

@Transactional 어노테이션 덕분에 테스트가 끝나면 데이터베이스는 원래 상태로 롤백되어, 다른 테스트에 영향을 주지 않습니다.

### 3. 나중에 구현해볼 것 : API 테스트와 포스트맨(Postman)

지금까지는 코드 내부의 로직과 DB 연동을 테스트했습니다. 다음 단계인 컨트롤러(Controller) 구현이 끝나면, 우리는 외부에서 HTTP 요청을 보내 실제 API가 잘 동작하는지 확인해야 합니다.

웹 브라우저만으로는 POST, PUT 같은 요청을 보내기 어렵기 때문에, 우리는 Postman이라는 API 테스트 도구를 사용할 것입니다. Postman을 사용하면 개발 중인 API를 마치 프론트엔드 클라이언트처럼 호출하고 그 결과를 편리하게 확인할 수 있습니다.

 

다음 포스트에서는 Controller 의 구현을 작성하겠습니다.