이전의 TDD를 공부한 내용을 토대로 실무에서 레거시 코드에 TDD를 적용해보자!!!
김창준님의 함께 자라기를 읽었었는데 그 책에서 나오는 내용 중 개인적인 공부와 업무를 따로 두지말라는 말이 있었는데, 이 말이 상당히 인상깊었다.
그래서 실천하고자 이전에 배웠던 TDD 내용들을 실무에서 적용해보려 한다.
내가 속해있는 회사는 10명이 안되는 규모이고, 개발팀은 나포함 3명이다. 그러기에 소수의 인원만 설득하면 실행할 수 있다는 장점이 있어서 이 점을 살려보려고 노력하고 있다.
먼저 팀원과의 회의에서 TDD의 필요성에 대한 내용은 이전에도 나온 적이 있었다. 업데이트하고 반영하다가 다른 곳에서 나온 이슈를 잡을 수 없어요.
, 모든 기능을 QA 진행한다는 것은 불가능에 가까워요.
, 일부라도 테스트를 조금씩 만들 필요성이 있어보여요.
라는 논의가 나왔었고, 실무와 레거시 코드에 관련된 글 들을 많이 찾아보았고 상의 끝에 우리만의 TDD를 레거시 코드에 적용하는 방식을 정리했다.
큰 원칙은 위에 3가지이다.
굳이 TDD를 만들겠다고 모든 코드에 단위테스트를 만드는 것보다 실제 코드를 손 볼 일이 있을 때 테스트 코드를 추가하고, 리팩토링을 무조건 적용하는 방식으로 단위 테스트를 조금씩 늘려나가는 전략을 세웠다.
그렇다면 본격적인 나의 실무에서 레거시 코드에 TDD를 적용하는 방식을 공유해본다.
실무 환경과 비슷한 블로그 환경으로 프로젝트를 만들어보았습니다.
초기 패키지는 아래와 같이 설정했습니다.
클래스 다이어그램은 아래와 같습니다.
간단하게 코드를 부가 설명하자면
Post
와 Category
사이에 따로 매핑 테이블을 만들고 각각 1:N 매핑
을 통해서 다대다를 대체하였습니다.@Setter
어노테이션을 사용하지 않고, @Builder
패턴을 사용하므로 필요한 데이터만 설정하고, 유연성 확보, 변경 가능성 최소화, 높은 가독성을 추구합니다.그렇다면 이제 실무 레거시 코드에 대해 알아보겠습니다.
최대한 실무와 비슷한 코드를 만들려했지만 너무 많은 기능을 붙여야하기 때문에 요약했고
많은 각색이 들어간점 양해 부탁드립니다.
카테고리
를 지정하고 댓글을 작성하면 카테고리
가 공부 카테고리
라면 장르 하위의 모든 게시글에 댓글이 달리고, 카테고리
가 아니라면 모든 게시글 중 하나만 댓글이 달리는 기능이다.
간단하게 단위 테스트를 작성할 내용을 초기 체크리스트로 만들어보면
이 정도이다.
먼저, TDD의 기능 구현은 쉬운 로직에서 어려운 로직으로 가라했으니 쉬운 부분부터 구현한다.
코드에서 보면
빨간 테두리 부분인데 먼저 users가 조회 안되는 경우를 먼저 하려한다.
구현하기 전 좀 더 체크리스트를 구체적으로 생각해보면
로 구체적으로 만들어주고, 단위 테스트를 만들어보자.
JUnit5
로 테스트 파일을 먼저 만들고,
Mock
으로 각 리포지토리를 reviewServiceImpl
에 주입해주고
단위 테스트를 위와같이 만들어준다. (Assertions
는 org.assertj.core.api.Assertions
라이브러리 사용)
실행해보면
초록불을 확인할 수 있다.
이미 코드에서 orElseThrow
로 예외처리를 잘 해놔서 간단하게 초록불을 확인할 수 있다.
이와 같이 userId가 없는 아이디인 경우
도 만들어준다.
단위 테스트를 실행하면
단위 테스트 통과했으니 전체 실행해보면
모두 초록불을 확인
한가지 큰 체크리스트를 끝내고, 위의 빨간 테두리 안에 포함되는 두번째 category가 조회 안되는 경우
를 진행해본다.
세부적인 체크리스트를 만들어보면
유저와 같이 두개 정도만 떠올라 체크리스트를 만들어주고, 추후 생각나는 경우는 그때그때 만들어주기로한다.
단위테스트를 작성하는데 Category
코드로 내려오기 위해서는 Users
조회 코드가 무사히 지나갈 수 있어야 한다. 그리고 Category
조회 코드에서 Exception
이 발생하지만 후에 users
가 필요하니 미리 MockData
를 만들어주자.
@BeforeEach
어노테이션 통해 각 테스트 시작 전 MockData
를 만들어주고자 한다.
만들어주는 과정에서 모든 entity
의 아이디를 @GeneratedValue
의 디폴트인 AUTO
전략을 가지고 자동으로 아이디를 자동으로 한개씩 증가하기 때문에 따로 @Builder
생성자 부분에 아이디를 넣지 않아 id를 지정할 수 가 없다.
이미 실무에서는 모든 엔티티에서 같은 전략으로 엔티티를 생성중이어서 전략을 바꿀 수 없고, 필요한 부분의 엔티티만 상속을 통해서 setId()
를 할 수 있도록 만들어주었다.
부모 클래스를 만들어주고, 부모 클래스와 매핑은 하되, 상속을 통해 칼럼만 매핑정보로 제공받는 @MappedSuperclass
어노테이션을 사용한다.
그리고 기존 entity에 ID
어노테이션 칼럼은 지워주고 부모 클래스를 상속한다.
이렇게 기존 엔티티를 리팩토링해주고
카테고리 관련 단위 테스트를 만들어주면 초록불을 확인할 수 있다.
이제 초기 체크리스트 중 쉬운 부분을 테스트로 구현했고, 엔티티의 구조를 바꾸는 리팩토링도 진행했다. 다시 체크리스트를 보면
이제 다음으로 category가 공부인 경우
를 단위 테스트 작성해보자
코드에서는 빨간 네모 부분이다.
assert 부분은 reviewCount
와 reviewList
로 확인하고,
세부적인 체크리스트를 만들어보면
mapCategoryAndPostList가 없는 경우
먼저 만들어보면
처음에 category
를 통해 MapCategoryAndPostRepository
에서 findById
를 사용하므로 카테고리를 먼저 만드는 메소드를 만들어주고 Mock
을 해주었다.
mapCategoryAndPostList
만 비어있으면 되므로 코드의 오류가 발생하지 않아 보여 이렇게 작성하고, 실행시키면
따로 예외처리 하지 않아도 정상 작동되는 레거시였기 때문에 그냥 바로 초록불을 확인할 수 있다.
간단하게 아래 then
부분만 리팩토링해보면
이렇게 만들고 전체 테스트에서 초록불을 확인한 후 다음 체크리스트로 넘어간다.
일단 Mock
데이터를 만들어보면
Post -> Review 생성 -> ReviewList 생성
-> Post에 ReviewList 업데이트 -> Category와 Post Map 생성
-> Category와 Post Map List 생성
단위 테스트 코드를 작성하면
Post
두개니까 Review
두개 생성되게 Assert
문을 작성하고 실행하면
전체 테스트 모두 초록불을 확인하고 편안해진다.
마지막으로 카테고리가 공부가 아닌 경우
를 테스트 해보자
체크리스트를 좀 구체화해보면 category가 공부인 경우
와 동일해 보인다.
먼저 두개의 체크 사항이 카테고리가 공부인 경우
와 비슷하니
일단 복사하고, categoryId
와 예상되는 Assert
결과를 바꿔준다.
둘 다 실행해보면
mapCategoryAndPostList가 없는 경우
는 초록불
mapCategoryAndPost가 있는 경우
는 초록불을 확인할 수 있다.
원인을 찾아보면
카테고리 아이디가 1L
이어서 else
를 타지 못하는 것으로 여겨져
테스트 코드를 리팩토링해준다.
categoryId
를 1L
이 아닌 다른 아이디로 설정해주어서 다시 실행해보면
초록불을 확인했고
전체 테스트 실행해보면
모두 초록불을 확인할 수 있다.
체크리스트를 다시 확인해보면
모든 체크리스트를 완수했다.
이제 서비스 로직의 리팩토링을 해보자.
분량 조절 실패로 리팩토링은 다음 글에 남기겠습니다.
참고 : 개발일상 - 레거시에 통합 테스트 붙이기,
레거시 코드에 테스트코드 추가와 리팩토링하기,
Testing and Refactoring Legacy Code
[Java] 빌더 패턴(Builder Pattern)을 사용해야 하는 이유
@ManyToMany를 사용하면 안 되는 이유