본문 바로가기
IT이야기/LANGUAGE

[Java/Spring] 개선된 아키텍쳐로 리팩토링 & 자바 테스트

by JI_NOH 2024. 1. 21.

인프런 - Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 / 섹션4 강의

 

 

레이어드 아키텍쳐에서 (직관적인 형태) 서로 역의존성을 주입한 상태로 바꾸기 위해 개선된 아키텍쳐로 변경하는 작업을 끝내고 -추상화작업- 그에 맞춰 테스트에도 새로운 방식을 적용하게 되었다.

 

우선 뜯어고치는 단계에 대해 설명해주시는데 그 부분 덕에

깃허브 코드들 종종 보면 그놈의 Impl 클래스가 왤케 많냐~~~~~~ 싶었던 부분이 해소됐다.

 

인프런 - Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트

 

 

이게 의식의 흐름대로 구조를 직관적으로 짜게 되면 나오는 아키텍쳐다.

실제로 내가 첫 프로젝트를 생성할 때도 그렇게 진행을 했었다.

 

그런데 이처럼 구조를 만들게 되면 테스트를 하게 될 때 DB를 자꾸 연결해서 테스트 할 수 밖에 없고

테스트하고 싶은건 Repository인데 Service를 주입받아야한다거나

그러기 위해서 또 Service구현을 해줘야 한다거나 등등의 번거로운 점이 생긴다.

 

이렇게 되면 소형테스트가 되지 않는 것이 문제다.

 

 
인프런 - Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트

 

해서 테스트용을 위해 상속되어야 할 로직은 interface로 생성하고

그를 상속한 클래스를 별도로 만들어 사용하는 것이 좋다. 이렇게 되면 역의존성 주입이 가능해 진다.

 

이는 Repository에도 적용이 된다.

 

사실 처음엔 이 부분이 상당히 어려웠다. DB없이 테스트를 할 수 있는 구조를 만들기 위해 여러 클래스, 인터페이스를 만들어서 상속에 상속을 꼬리 물어서 실제로 Service단에서 구현할 때도 헷갈렸음ㅠㅠ

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Repository의 상속 구조

JpaRepository를 직접적으로 상속하는 UserJpaRepository 인터페이스

- 이는 차후 Jpa를 이용한 것이 아닌 Mybatis 등 다른 DB Connection방식을 쓸 때를 위한(확장성) 절차라고 봐도 좋다.

public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
 

 

다른 클래스에서 끌어다 쓸 UserRepository 인터페이스

- 위에서 말한 Jpa가 아닌 Mybatis를 쓰더라도 변경되지 않을 인터페이스다.

public interface UserRepository {

    Optional<User> findByIdAndStatus(long id, UserStatus userStatus);

    Optional<User> findByEmailAndStatus(String email, UserStatus userStatus);

    User save(User user);

    Optional<User> findById(long id);
}
 

 

UserRepository 인터페이스의 구현부 클래스

 

- JpaRepository를 쓴다면 UserRepository를 빈등록하여 쓰면 되고,
   Mybatis를 쓴다면 그에 맞는 Repository 인터페이스를 빈 등록하여 쓸 수 있다.

@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository{

    private final UserJpaRepository userJpaRepository;

    //UserRepository구현
 

이런 상속 구조를 가지고 있다.

 

 

그리고 이렇게 되면 테스트에서는

@DataJpaTest
@Sql("/sql/user-repository-test-data.sql")
class UserJpaRepositoryTest {

    @Autowired
    private UserJpaRepository userRepository;

    @Test
    void findByIdAndStatus_데이터가있으면_가져오는지확인(){
        //g

        //w
        Optional<UserEntity> res = userRepository.findByIdAndStatus(1, UserStatus.ACTIVE);

        //t
        Assertions.assertThat(res.isPresent()).isTrue();
    }
 

이렇게 UserJpaRepository만 생성해서 제대로 동작하는 지 확인할 수 있다. 근데 솔직한 말로 이건 JPA내에서 쿼리를 알아서 짜주는 거라 테스트가 큰 의미가 있나 라는 생각이 사실 좀 든다 ^^..

그런만큼 이정도까지 상속을 나눌 필요도 있나라는 의구심도 살짝 들고..?

 

 

 

이번 프로젝트 리팩토링 과정 & 테스트 변경작업 중에 겪은 상황

    @Test
    void userUpdateDto를_이용하여_유저수정(){
        UserUpdateDto updateDto = UserUpdateDto.builder()
                .address("Incheon")
                .nickname("test-I")
                .build();

        userService.update(3, updateDto);

        User userEntity = userService.getById(3);

        Assertions.assertThat(userEntity.getId()).isNotNull();
        Assertions.assertThat(userEntity.getAddress()).isEqualTo("Incheon");
        Assertions.assertThat(userEntity.getNickname()).isEqualTo("test-I");
 

 

 

UserSerivce테스트 중,

업데이트 후 nickname 비교에서 널값이 떨어져서 update수정하는 중에 문제가 생겼나 하고 봤더니

 

최소 UserSerivce.update()함수에는 문제가 없어보인다.

    @Transactional
    public User update(long id, UserUpdateDto userUpdateDto) {
        User user = getById(id);
        user = user.update(userUpdateDto);
        user = userRepository.save(user);
        return user;
    }
 

그러면 User.update()쪽이 문제가 있어보이는데

    public User update(UserUpdateDto userUpdateDto){
        return User.builder()
                .id(id)
                .email(email)
                .nickname(userUpdateDto.getNickname())
                .address(userUpdateDto.getAddress())
                .certificationCode(certificationCode)
                .status(status)
                .lastLoginAt(lastLoginAt)
                .build();
    }
 

여기도 딱히 빠진 요소 없이 잘 들어가 있는 것 같다. 그럼 대체 어디인가..?

차선으로는 userRepository.save(user)여기인가?

그럼 create도 문제가 됐어야 하지 않을까?!

@Override
    public User save(User user) {
        return userJpaRepository.save(UserEntity.fromModel(user)).toModel();
    }
 

혹시나해서 체크하러 갔다.

너무 단순한 함수구조인데 이제 슬슬 결말에 다 다른 것 같다.

 

UserEntity.fromMode(user)여기까지 흘러들어갔다.

 

그리고 여기서 userEntity.nickname = user.getNickname(); 줄이 빠져있는 것을 발견하고 추가했다.

 

이런 것이 테스트의 진정한 순기능이 아닌가 한다.

 

물론 사람이 하다보면 실수 하기 마련이라 왜 꼼꼼한 테스트가 중요한지.

잘 짜둔 테스트가 이런 사소한 실수를 잡아낼 수 있는지를 느낄 수 있는 기회였다.