프로젝트4

코드 굽는 제빵사·2021년 1월 13일
0
post-thumbnail

여러개의 레포를 갖고 있는 서비스 테스트?

어제 오늘은 아래와 같은 서비스의 메서드를 테스트 하려고 햇습니다.
스토어를 만들 때 연관관계가 있는 클레스의 인스턴스를 같이 만드는 메서드를 만들었습니다.
처음엔 위에서 배운 것 처럼 Mock를 활용해서 테스트하면 될거라고 생각했습니다.

@Service
@Transactional
@RequiredArgsConstructor
public class StoreCommandServiceImpl implements StoreCommandService {

    private final StoreRepository           storeRepository;
    private final EmployerRepository        employerRepository;
    private final JobOpeningRepository      jobOpeningRepository;
    private final PostManagementRepository  postManagementRepository;

    @Override
    public UUID create(UUID employerId, StoreCmdDto dto) {
        Employer employer = employerRepository.findById(employerId).orElseThrow();
        Store newStore = new Store(dto.getName(), dto.getAddress(), employer);
        Store saveStore = storeRepository.save(newStore);
        jobOpeningRepository.save(new JobOpening(newStore));
        postManagementRepository.save(new PostManagement(newStore));
        return saveStore.getId();
    }
}

해결 : 상황에 맞게 Mock 또는 Springboot!

Mock를 활용해서 해결 하려고 했는데 repository가 레포지토리에 맞는 객체들을 미리 생성해야했고
repo에 해당하는 메서드가 호출 될때마다 행동을 지정 해줘야 했습니다.
테스트를 위해서 굉장히 많은 코드를 작성해야 했기에 테스트를 하는게 아니라 테스트를 위해 코드를 작성 하는 것 같았습니다. Mock를 활용한 코드도 적어두면 좋은데 이미 Springboottest로 변경하고 컨트롤 + Z 해봤지만 복구의 한계가 있어서 말로 대신하겠습니다.

아래에 첨부한 자료를 보고 SpringbootTest를 사용하기로 하였습니다.

    @Test
    public void createStore() throws Exception {
        //given
        Employer employer = new Employer("seouler",
                createAddress("seoul", "songpa", "42"));
        employerRepository.save(employer);
        StoreCmdDto storeCmdDto = new StoreCmdDto("starbucks",
                createAddress("busan", "gaepo", "24"));
        //when
        UUID uuid = storeCommandService.create(employer.getId(), storeCmdDto);
        //then
        Store findStore = storeRepository.findById(uuid).orElseThrow();
        JobOpening findJobOpening = jobOpeningRepository.findByStore(findStore).orElseThrow();
        PostManagement findPostManagement = postManagementRepository.findByStore(findStore).orElseThrow();
        assertEquals(findStore.getName(), storeCmdDto.getName());
        assertEquals(findStore.getAddress(), storeCmdDto.getAddress());
        assertEquals(findStore.getId(), findJobOpening.getStore().getId());
        assertEquals(findStore.getId(), findPostManagement.getStore().getId());
    }

저는 연관관계 외래키 관계가 store에서 cascade를 사용해서 영속성 전이를 사용하면 되지 않을까?란 생각을 했는데 store에는 JobOpening, PostManagement의 양방향으로 할 필요성이 없어보여서 그렇게 하지 못했습니다. 그래서 결국 서비스 내에서 다른 repo를 불러서 save해주는 방식으로 객체를 만들어 주었습니다. Mock를 사용하지 않아서 객체들을 많이 만들지 않아서 더 짧은 코드로 테스트할 수 있었습니다.

참고 자료
내가 Mock 대신 SpringBootTest를 선택한 이유
TDD 무료 책 링크

문제 : 너무 많은 repo를 쓰는 것 아닌가?

저의 원래 계획은 아래와 같이 연관관계를 맺고 그것에 따라 메서드를 만드려고 했습니다.

위의 문제는 테스트 방법을 바꾸면서 해결 했지만 올바른 해결 방법은 아니라고 생각했습니다.
나름 그림을 그리고 시작했는데 위의 문제처럼 많은 repo를 사용하게 되고 그에 따라 테스트도 하기 어려웠습니다. 그렇다면 내가 처음부터 설계를 잘못 한 것 아닌가 의심되기 시작했습니다.

public class StoreCommandServiceImpl implements StoreCommandService {

    private final StoreRepository           storeRepository;
    private final EmployerRepository        employerRepository;
    private final JobOpeningRepository      jobOpeningRepository;
    private final PostManagementRepository  postManagementRepository;

    @Override
    public UUID create(UUID employerId, StoreCmdDto dto) {
        Employer employer = employerRepository.findById(employerId).orElseThrow();
        Store newStore = new Store(dto.getName(), dto.getAddress(), employer);
        Store saveStore = storeRepository.save(newStore);
        jobOpeningRepository.save(new JobOpening(newStore));
        postManagementRepository.save(new PostManagement(newStore));
        return saveStore.getId();
    }
}

해결 : 생각의 기준을 바꿔보자!

우선 PostManagement와 JobOpening의 역할을 하나로 합쳐도 될 것 같았습니다.
그래서 둘을 합쳐서 StoreManagement라는 새로운 객체를 만들었습니다.

제가 처음에 클레스를 작게 작게 쪼갠 이유는 각 하나의 역할을 맡도록 해야겠다고 생각했기 때문이었습니다.
그리고 연관관계의 주인이 store가 아니기 때문에 store를 save하면서 cascade 영속성 전이를 사용하지 못 할 것이라고 생각했습니다. 그래서 많은 repo를 사용 할 때 스토어 객체를 새로 만들고 그 객체들을 모두 다른 레포들에게 전달해주었습니다.
그렇게 고민 고민하다 꼭 storeService지만 꼭 store가 저장의 주체가 아니어도 되지 않겟다고 생각했습니다. 그래서 StoreManagement를 중심으로 다시 코드를 리팩토링 했습니다.

public class StoreCmdServiceImpl implements StoreCmdService {

    private final EmployerRepository        employerRepository;
    private final StoreManagementRepository storeManagementRepository;

    @Override
    public UUID create(UUID employerId, StoreCmdDto dto) {
        Employer employer = employerRepository.findById(employerId).orElseThrow();
        Store store = new Store(dto.getName(), dto.getAddress(), employer);
        StoreManagement storeManagement = new StoreManagement(store);
        storeManagementRepository.save(storeManagement);
        return store.getId();
    }
}

참고 자료 (인스턴스 변수가 3개이상이면 안된다고 하셔서 이상함을 정확하게 파악하게 된 계기)
[우아한테크세미나] 190425 TDD 리팩토링 by 자바지기 박재성님

0개의 댓글