-
Notifications
You must be signed in to change notification settings - Fork 2
refactor: CompletedCoursesService (책임 분리, 테스트 코드 개선) #91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: CompletedCoursesService (책임 분리, 테스트 코드 개선) #91
Conversation
- C.C.S가 시큐리티에 의존하지 않도록 사용자 학번만 입력받도록 변경 - 변수명에 컬렉션(List)이 포함되지 않도록 변경
gmltn9233
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
분리하니까 로직이 더 한눈에 들어오네요. 좋습니다~~👍
| //TODO 이 메서드는 어떠한 용도로 사용되는 것이며, 지워도 되는 것인지 확인하기 | ||
| // raw file 입력 용 | ||
| private Optional<GonghakCoursesDomain> mapToGonghakCoursesDomain(String[] data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 메서드는 크롤링을 통해 얻은 csv 데이터를 로직에 맞게 변환하여 gonghakCourse로 변환하는 메서드입니다.
제일 처음 지섭이형이 만든 로직은 이 메서드를 이용하여 raw file을 바로 넣어서 해당 메서드를 통해 DB에 삽입하였습니다.
현재 이렇게 삽입된 DB에서 csv파일을 export 하여 운영 DB에 import 하는 식으로 진행하였는데, #62 에서 해당 csv 파일을 이용해 개발환경을 통합하는 과정에서 해당 메서드가 사용되지 않게 되었습니다.
하지만 이후 학과 추가라던지 사용될 일이 있을것 같아 남겨두었습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확인했습니다 👍
| public static MockMultipartFile 기이수성적조회_파일_생성(final String filePath) throws IOException { | ||
| String fileName = "기이수성적조회"; | ||
| File file = new File(filePath); | ||
| return new MockMultipartFile(fileName, file.getName(), "xlsx", new FileInputStream(file)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 파일 업로드를 이렇게 테스트할 수 있군요..
| UserDetails userDetails = (UserDetails) authentication.getPrincipal(); | ||
| List<CompletedCoursesDomain> beforeDataList = excelService.getExcelList(userDetails); | ||
| model.addAttribute("datas",beforeDataList); | ||
| Long studentId = Long.parseLong(userDetails.getUsername()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
service로 userDetails를 넘겨주는 방식에서 studentId를 넘겨주는 방식으로 리팩토링하였는데, 로직에 studentId 만 사용되기때문에 studentId를 직접 넘겨주는 방식으로 리팩토링한건가요??
관련해서 userDetails 을 넘겨주는 방식이 맞을지 studentId를 넘겨주는 방식이 맞을지 고민해보았습니다.
혹시 모를 확장성이나 책임 측면에서 controller에서 userDetails에서 studentId를 추출하는 로직을 가지고 있는것이 적절하지 않다고 생각이 들었습니다. 허나 그렇게 생각하면 userDetails의 원본인 authentication을 넘겨줘야 하나?
근데 그건 또 너무 불필요하게 높은 추상화라는 생각이 들었습니다.
관련해서 의견을 듣고싶습니다. 사실상 studentId 말고는 service에서 이용할 일이 없긴 하니 현재 방식이 적절할까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 코드를 보고 의문이 들었던 점은 다음과 같았습니다.
CompletedCoursesService가 시큐리티 관련 의존성을 가져야 할까? 였습니다.
사실 이번에 CompletedCoursesService를 리팩토링하면서 중점적으로 가졌던 부분이기도 합니다.
CompletedCoursesService는 Controller로부터 기이수 과목을 조회하라는 메시지를 전달받는데, 이 책임을 수행하기 위해 필요한 정보만 알면 되지 않을까라는게 저의 생각이었습니다.
만약, studentId가 아니라 userDetails를 넘기도록 한다면, 테스트 코드를 작성하는 데에도 userDetails에 대한 추가 작업이 필요합니다.
@Test
@DisplayName("기이수 성적 파일을 업로드하면, 기이수 성적 과목이 저장된다.")
public void saveCompletedCoursesTest() throws IOException {
//given
MockMultipartFile testFile = 기이수성적조회_파일_생성("src/test/resources/file/기이수성적조회.xlsx");
//when
completedCoursesService.saveCompletedCourses(testFile, TEST_ID);
//then
List<CompletedCoursesDomain> findCourses = completedCoursesService.getCompletedCourses(TEST_ID);
assertThat(findCourses.size()).isEqualTo(TEST_FILE_ROW_SIZE);
}현재는 학번만 전달하면 기이수 과목을 조회하는 메서드를 호출할 수 있기 때문에 테스트 코드가 간단합니다.
만약, getCompletedCourses가 단순히 학번이 아니라 userDetails를 넘겨줘야 한다면, 기이수 서비스에 관한 테스트에 도 시큐리티 의존성을 추가해야 합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트 코드 측면에서 확실히 studentId를 넘기는 쪽이 이점이 더 많네요... 확인했습니다!
| .userDomain(userDomain) | ||
| .coursesDomain(coursesDomain) | ||
| .year(userCourse.year()) | ||
| .semester(userCourse.semester()) | ||
| .build(); | ||
| saveCourses.add(data); // 엔티티를 리스트에 추가 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
새로운 service를 만들었음에도 dto를 사용해서 깔끔하게 처리하는 모습 좋습니다👍
🛠️작업 내용
현재 코드에서 발견한 문제점
1. 테스트의 의도가 불분명하다
기존에 있던 CompletedCoursesService, 이하 CCS에 대한 테스트 메서드는 다음과 같이 여러 Dao의 메서드를 호출하며, 기이수 과목이 효과적으로 저장되는지 테스트하고 있습니다.
하지만, 여기에는 CCS의 로직을 호출하지는 않고 있습니다. 따라서 이것이 CCS 로직에 대한 테스트 코드라고 판단되지는 않았습니다.
2. CompletedCoursesService가 많은 책임을 갖고 있다.
CompletedCoursesService, 이하 CCS는 기이수 과목(CompletedCourse) 이외에도 다양한 의존성을 갖고 있습니다.
위 두 메서드만 보더라도, CCS는 엑셀 파일의 확장자, 형식에 대해서도 알고 있습니다.
CCS는 엑셀 파일에 저장된
년도, 학기, 학수번호데이터만 필요한 것이며, 이들이 엑셀 파일에서 어떠한 순서로 저장되어 있는지 까지는 알지 않아도 된다고 생각했습니다.CCS가 기이수 과목을 다루는 책임만 갖는다면, 엑셀 파일의 형식이 변경되더라도 그 영향을 받지 않을 것입니다. (SRP of SOLID)
해결한 방법
1. given/when/then 패턴 적용
given-when-then 구조를 적용하여 각각의 테스트 메서드가 테스트하고자 하는 것이 무엇인지를 한 눈에 알아볼 수 있도록 개선하였습니다. when절에는 테스트하고자 하는 메서드만 존재합니다.
2. 테스트를 문서화하기
테스트 코드만으로 해당 어플리케이션이 대략적으로 어떠한 기능을 수행하는지를 알 수 있도록
@DisplayName의 내용을 문장으로 표현하였습니다.해당 테스트 코드를 보고,
성적파일을 업로드했던 유저가 다시 업로드하면, 기존 데이터가 초기화되고, 다시 저장되는구나라는 걸 바로 알 수 있습니다.테스트 메서드를 한글로 표현하는 방법도 있지만, 이는 특정 테스트 메서드를 재사용할 경우, 효과적이라고 판단했습니다. 이를 제외한 경우에는 테스트 메서드를 한글로 작성하면 단어 사이에
언더바(_)를 붙여야 해서 개인적으로는 번거로운 것 같습니다.테스트 메서드를 한글로 작성
3. File과 관련된 책임을 별도의 객체로 분리
AS-IS
TO-BE
테스트 커버리지 개선
리팩토링 전 : CompletedCoursesServiceDataTest의 라인 커버리지 : 14%

(왼쪽부터 클래스 / 메서드 / 라인에 대한 커버리지입니다.)
리팩토링 후
✔️PR 체크리스트