Skip to content
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

[Feature] - BE 테스트 개선 1단계 Test Fixture 운용 방식 통일 (Enum Fixture) #614

Merged
merged 37 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
47b71e2
test: OauthUserFixture - Enum 기반의 Fixture로 전환
Libienz Nov 19, 2024
e446ed7
test: MemberFixture - EnumFixture 컨벤션 통일 개선
Libienz Nov 19, 2024
26c535d
test: MemberFixture 속성을 가지는 Member를 만드는 Request 객체 생성하는 기능 추가
Libienz Nov 19, 2024
739b5a6
test: MemberFixture 속성을 기반으로 Request 객체를 만들도록 테스트 코드 수정
Libienz Nov 19, 2024
adb1607
test: OauthUserFixture의 팩토리 메서드 이름 수정 (createXXX -> getXXX)
Libienz Nov 19, 2024
ac6904f
test: MemberFixture의 팩토리 메서드 이름 수정 (createXXX -> getXXX)
Libienz Nov 19, 2024
5a02135
test: 사용되지 않는 Fixture 클래스 제거 개선
Libienz Nov 19, 2024
521c494
test: Travelogue관련 Enum Fixture 컨벤션 통일 개
Libienz Nov 19, 2024
1bbcaed
test: TravelogueCountry, TravelogueDay 도메인 테스트 중복 데이터 셋팅 제거 개선
Libienz Nov 19, 2024
f0b74bf
test: Request 객체 생성 도메인 Fixture 내부로 응집
Libienz Nov 20, 2024
123a7ba
test: 도메인 구성 시 연관관계를 직접 주입하도록 테스트 코드 수정
Libienz Nov 29, 2024
99963e0
test: CountryCode 패키지 위치 수정
Libienz Nov 30, 2024
bf0b043
test: TraveloguePhotoTest 연관관계 도메인 주입을 셋업 코드로 이동 개선
Libienz Nov 30, 2024
db795f0
test: Fixture를 통한 도메인 생성 시 연관관계를 주입하도록 로직 수정
Libienz Nov 30, 2024
807247d
chore: develop 브랜치의 작업들과 병합
Libienz Nov 30, 2024
0b29c38
test: 사용하지 않는 상수 제거 개선
Libienz Dec 2, 2024
a136269
test: 반복되는 메서드 모킹 setUp으로 이동
Libienz Dec 2, 2024
f63349b
test: 반복되는 메서드 모킹 setUp으로 이동
Libienz Dec 2, 2024
b8aae21
test: 연관 관계를 가지는 Fixture를 주입받아 Request객체를 생성하는 기능 추가
Libienz Dec 4, 2024
f54079b
test: TravelogueRequestBuilder 구현
Libienz Dec 4, 2024
6b83c22
test: TravelogueRequestBuilder에서 DayBuilder 체크로직 제거
Libienz Dec 4, 2024
5b80e2d
test: 실패하는 테스트 수정
Libienz Dec 4, 2024
de7e81a
test: RequestBuilder 객체 패키지 이동 fixture -> helper
Libienz Dec 4, 2024
d206e14
test: TravelPlan관련 fixture에 연관 도메인 관련 내용 제거
Libienz Dec 4, 2024
0b51fc3
test: TravelPlaceTodoFixture 구현
Libienz Dec 4, 2024
72d9350
test: Fixture를 통해서 Request 객체 만드는 기능 구현
Libienz Dec 4, 2024
f854dec
test: TravelPLanRequestBuilder 구현
Libienz Dec 4, 2024
e23ce11
test: 과거 날짜 여행기 픽스쳐 추가
Libienz Dec 4, 2024
99cf179
test: Builder 클래스 개행 수정
Libienz Dec 4, 2024
2c0198d
test: TravelPlanControllerTest에서 Request를 생성 시 RequestBuilder를 이용하도록 개선
Libienz Dec 4, 2024
8e590b0
Merge branch 'develop/be' into feature/be/#602
Libienz Dec 4, 2024
5b49640
test: 사용하지 않는 테스트 상수 제거 개선
Libienz Dec 5, 2024
f24222f
test: TravelogueCountryTest 줄바꿈 컨벤션 적용
Libienz Dec 5, 2024
a9e5902
test: TraveloguePlaceFixture 줄바꿈 컨벤션 적용 및 사용하지 않는 메서드 제거 개선
Libienz Dec 5, 2024
18a6e65
test: TravelogueRequestBuilder와 TravelogueDayRequestBuilder 별도 클래스로 분리
Libienz Dec 5, 2024
4b9730f
test: TravelPlanRequestBuilder와 PlanDayRequestBuilder 별도 클래스로 분리
Libienz Dec 5, 2024
d7ec90f
test: MemberFixture 이름 변경 DEFAULT_MEMBER -> TOUROOT_LOCAL_USER
Libienz Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import kr.touroot.authentication.dto.request.TokenReissueRequest;
import kr.touroot.authentication.dto.response.OauthUserInformationResponse;
import kr.touroot.authentication.dto.response.TokenResponse;
import kr.touroot.authentication.fixture.OauthUserInformationFixture;
import kr.touroot.authentication.fixture.OauthUserFixture;
import kr.touroot.authentication.helper.LoginTestHelper;
import kr.touroot.authentication.infrastructure.JwtTokenProvider;
import kr.touroot.authentication.infrastructure.KakaoOauthProvider;
Expand Down Expand Up @@ -47,8 +48,9 @@ void setUp() {
@DisplayName("카카오 로그인 요청을 처리할 수 있다")
@Test
void loginTest() throws Exception {
OauthUserInformationResponse kakaoUserInformation = OauthUserFixture.KAKAO_USER.getOauthInformationResponse();
when(oauthProvider.getUserInformation(any(String.class), any(String.class)))
.thenReturn(OauthUserInformationFixture.USER_1_OAUTH_INFORMATION);
.thenReturn(kakaoUserInformation);

RestAssured.given().log().all()
.queryParam("code", "test")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kr.touroot.authentication.fixture;

import kr.touroot.authentication.dto.response.OauthUserInformationResponse;
import kr.touroot.authentication.dto.response.kakao.KakaoAccount;
import kr.touroot.authentication.dto.response.kakao.KakaoProfile;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public enum OauthUserFixture {

KAKAO_USER(1L, "test_nickname", "https://test_img_src.com");

private final Long socialLoginId;
private final String nickname;
private final String profileImagePath;

public OauthUserInformationResponse getOauthInformationResponse() {
KakaoProfile kakaoProfile = new KakaoProfile(nickname, profileImagePath);
KakaoAccount kakaoAccount = new KakaoAccount(kakaoProfile);
return new OauthUserInformationResponse(socialLoginId, kakaoAccount);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class LoginTestHelper {
private final MemberRepository memberRepository;

public Member initMemberTestData() {
Member member = MemberFixture.DEFAULT_MEMBER.build();
Member member = MemberFixture.TOUROOT_LOCAL_USER.getMember();
return memberRepository.save(member);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import java.util.Optional;
import kr.touroot.authentication.dto.response.LoginResponse;
import kr.touroot.authentication.dto.response.TokenResponse;
import kr.touroot.authentication.fixture.OauthUserInformationFixture;
import kr.touroot.authentication.fixture.OauthUserFixture;
import kr.touroot.authentication.infrastructure.JwtTokenProvider;
import kr.touroot.authentication.infrastructure.KakaoOauthProvider;
import kr.touroot.member.domain.Member;
Expand All @@ -28,7 +28,7 @@ class LoginServiceTest {

private static final String AUTHENTICATION_CODE = "test-authentication-code";
private static final String REDIRECT_URI = "http%3A%2F%2Flocalhost%3A8080%2Fapi%2Fv1%2Flogin%2Foauth%2Fkakao";
private static final Member MEMBER = MemberFixture.KAKAO_MEMBER.build();
private static final Member MEMBER = MemberFixture.KAKAO_MEMBER.getMember();

@InjectMocks
private LoginService loginService;
Expand All @@ -47,7 +47,7 @@ void existUserKakaoSocialLoginTest() {
String refreshToken = "bbb";

when(kakaoOauthProvider.getUserInformation(any(String.class), any(String.class)))
.thenReturn(OauthUserInformationFixture.USER_1_OAUTH_INFORMATION);
.thenReturn(OauthUserFixture.KAKAO_USER.getOauthInformationResponse());
when(memberRepository.findByKakaoId(any(Long.class)))
.thenReturn(Optional.of(MEMBER));
when(jwtTokenProvider.createToken(MEMBER.getId()))
Expand All @@ -69,7 +69,7 @@ void nonExistUserKakaoSocialLoginTest() {
String refreshToken = "bbb";

when(kakaoOauthProvider.getUserInformation(any(String.class), any(String.class)))
.thenReturn(OauthUserInformationFixture.USER_1_OAUTH_INFORMATION);
.thenReturn(OauthUserFixture.KAKAO_USER.getOauthInformationResponse());
when(memberRepository.findByKakaoId(any(Long.class)))
.thenReturn(Optional.empty());
when(memberRepository.save(any(Member.class)))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package kr.touroot.member.controller;

import static kr.touroot.member.fixture.MemberRequestFixture.EMPTY_EMAIL_MEMBER;
import static kr.touroot.member.fixture.MemberRequestFixture.EMPTY_NICKNAME_MEMBER;
import static kr.touroot.member.fixture.MemberRequestFixture.EMPTY_PASSWORD_MEMBER;
import static kr.touroot.member.fixture.MemberRequestFixture.EMPTY_PROFILE_IMAGE_URL_MEMBER;
import static kr.touroot.member.fixture.MemberRequestFixture.VALID_MEMBER;
import static org.hamcrest.Matchers.is;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import kr.touroot.global.AcceptanceTest;
import kr.touroot.member.dto.request.MemberRequest;
import kr.touroot.member.fixture.MemberFixture;
import kr.touroot.utils.DatabaseCleaner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.server.LocalServerPort;

Expand Down Expand Up @@ -42,7 +40,7 @@ void setUp() {
@DisplayName("회원 가입을 한다.")
@Test
void createTravelogue() {
MemberRequest request = VALID_MEMBER.getRequest();
MemberRequest request = MemberFixture.TOUROOT_LOCAL_USER.getCreateRequest();

RestAssured.given().log().all()
.contentType(ContentType.JSON)
Expand All @@ -56,7 +54,7 @@ void createTravelogue() {
@DisplayName("비어있는 이메일로 회원 가입하면 예외가 발생한다.")
@Test
void createTravelogueWithEmptyEmail() {
MemberRequest request = EMPTY_EMAIL_MEMBER.getRequest();
MemberRequest request = MemberFixture.TOUROOT_LOCAL_USER.getCreateRequestWithEmail("");

RestAssured.given().log().all()
.contentType(ContentType.JSON)
Expand All @@ -68,9 +66,10 @@ void createTravelogueWithEmptyEmail() {
}

@DisplayName("비어있는 비밀번호로 회원 가입하면 예외가 발생한다.")
@Test
void createTravelogueWithEmptyPassword() {
MemberRequest request = EMPTY_PASSWORD_MEMBER.getRequest();
@ParameterizedTest
@ValueSource(strings = {"", "\t", "\n"})
void createTravelogueWithEmptyPassword(String emptyPassword) {
MemberRequest request = MemberFixture.TOUROOT_LOCAL_USER.getCreateRequestWithPassword(emptyPassword);

RestAssured.given().log().all()
.contentType(ContentType.JSON)
Expand All @@ -82,9 +81,10 @@ void createTravelogueWithEmptyPassword() {
}

@DisplayName("비어있는 닉네임으로 회원 가입하면 예외가 발생한다.")
@Test
void createTravelogueWithEmptyNickname() {
MemberRequest request = EMPTY_NICKNAME_MEMBER.getRequest();
@ParameterizedTest
@ValueSource(strings = {"", "\t", "\n"})
void createTravelogueWithEmptyNickname(String emptyNickname) {
MemberRequest request = MemberFixture.TOUROOT_LOCAL_USER.getCreateRequestWithNickname(emptyNickname);

RestAssured.given().log().all()
.contentType(ContentType.JSON)
Expand All @@ -96,9 +96,11 @@ void createTravelogueWithEmptyNickname() {
}

@DisplayName("비어있는 프로필 사진 경로로 회원 가입하면 예외가 발생한다.")
@Test
void createTravelogueWithEmptyProfileImageUrl() {
MemberRequest request = EMPTY_PROFILE_IMAGE_URL_MEMBER.getRequest();
@ParameterizedTest
@ValueSource(strings = {"", "\t", "\n"})
void createTravelogueWithEmptyProfileImageUrl(String emptyProfileImageUrl) {
MemberRequest request = MemberFixture.TOUROOT_LOCAL_USER.getCreateRequestWithProfileImageUrl(
emptyProfileImageUrl);

RestAssured.given().log().all()
.contentType(ContentType.JSON)
Expand Down
48 changes: 43 additions & 5 deletions backend/src/test/java/kr/touroot/member/fixture/MemberFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,31 @@

import kr.touroot.member.domain.LoginType;
import kr.touroot.member.domain.Member;
import kr.touroot.member.dto.request.MemberRequest;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum MemberFixture {

KAKAO_MEMBER(1L, null, null, "https://dev.touroot.kr/temporary/profile.png", "리비",
LoginType.KAKAO),
DEFAULT_MEMBER(null, "[email protected]", "password", "https://dev.touroot.kr/temporary/profile.png", "뚜리",
LoginType.DEFAULT);
KAKAO_MEMBER(
1L,
null,
null,
"https://dev.touroot.kr/temporary/profile.png",
"리비",
LoginType.KAKAO
),
TOUROOT_LOCAL_USER(
null,
"[email protected]",
"password",
"https://dev.touroot.kr/temporary/profile.png",
"뚜리",
LoginType.DEFAULT
),
;

private final Long socialId;
private final String email;
Expand All @@ -21,10 +35,34 @@ public enum MemberFixture {
private final String nickname;
private final LoginType loginType;

public Member build() {
public Member getMember() {
if (loginType == LoginType.KAKAO) {
return new Member(socialId, nickname, profileImageUrl, loginType);
}
return new Member(email, password, nickname, profileImageUrl, loginType);
}

public MemberRequest getCreateRequest() {
return new MemberRequest(email, password, nickname, profileImageUrl);
}

Comment on lines +45 to +48
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enumFixture는 속성 값들을 거느리고 해당 fixture를 통해서 도메인 뿐만 아니라 위의 코드처럼 생성 요청 DTO를 만들 수도 있음을 안내드립니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

멋지네요! 확실히 Enum Fixture가 기존 방식보다 여러모로 유연하다는 생각이 듭니다.

public MemberRequest getCreateRequestWithEmail(String email) {
return new MemberRequest(email, password, nickname, profileImageUrl);
}

public MemberRequest getCreateRequestWithPassword(String password) {
return new MemberRequest(email, password, nickname, profileImageUrl);
}

public MemberRequest getCreateRequestWithNickname(String nickname) {
return new MemberRequest(email, password, nickname, profileImageUrl);
}

public MemberRequest getCreateRequestWithProfileImageUrl(String profileImageUrl) {
return new MemberRequest(email, password, nickname, profileImageUrl);
}

public MemberRequest getCreateRequestWithEmailAndNickname(String email, String nickname) {
return new MemberRequest(email, password, nickname, profileImageUrl);
}
Comment on lines +49 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트에 필요한 조합이 다르다보니 점층적인 생성자 오버로딩이 많아졌네요..
새로운 조합이 생길 때마다 또 다시 오버로딩 메서드가 생긴다는 유지보수 약점이 될 수도 있어 보입니다. 😢
Travelouge 쪽은 RequestBuilder를 도입을 하신걸로 보이는데 이런 부분도 Builder 패턴 도입 고민해보는 것도 좋을 것 같아 보입니다

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

builder를 사용해도 개선점이 크진 않을 것 같아요~ 미리 작성된 MemberFixture에 email하나, 닉네임 하나의 값만 바꾸고 싶은 경우에 사용하는 메서드들이어서요~

빌더를 통해 진행되어도 다음처럼 어색한 구문이 탄생할 수 있겠어요

MemberRequestBuilder.forMemberFixture(MemberFixture.LIBI)
    .setEmail("[email protected]")
    .build();

다만 Member에 새로운 속성이 추가되게 된다면 나머지 메서드들이 영향을 받는 다는 점에서 클로버가 지적해주신 유지보수 약점이 타당해 보이네요..

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eunjungL

빌더 역시 사용하게 되면 도메인 명세를 무시한 (특정 속성을 지니지 않은 데이터 생성 요청) 객체를 생성할 수 있는 취약점이 될 수 있을 것 같아서 우선은 트레이드 오프 지점이라고 결론 내렸습니다.

해당 코멘트는 반영하지 않을 예정인데 다른 의견 있으시면 알려주시면 감사하겠숨다

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유지보수 약점만 인지하고 넘어가도 좋을 것 같습니다~
리비의 말처럼 명세를 무시한 생성 가능성을 열어두는것도 좋지 않은 방법으로 보이네요 👍
개선할 수 있는 방안이 있는지 함께 고민해보시죠~

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package kr.touroot.member.helper;

import static kr.touroot.member.fixture.MemberFixture.DEFAULT_MEMBER;
import static kr.touroot.member.fixture.MemberFixture.TOUROOT_LOCAL_USER;

import kr.touroot.member.domain.Member;
import kr.touroot.member.repository.MemberRepository;
Expand All @@ -18,7 +18,7 @@ public MemberTestHelper(MemberRepository memberRepository) {
}

public Member persistMember() {
Member member = DEFAULT_MEMBER.build();
Member member = TOUROOT_LOCAL_USER.getMember();
return memberRepository.save(member);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package kr.touroot.member.service;

import static kr.touroot.member.fixture.MemberRequestFixture.DUPLICATE_NICKNAME_MEMBER;
import static kr.touroot.member.fixture.MemberRequestFixture.VALID_MEMBER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
Expand All @@ -17,6 +15,7 @@
import kr.touroot.member.domain.Member;
import kr.touroot.member.dto.request.MemberRequest;
import kr.touroot.member.dto.request.ProfileUpdateRequest;
import kr.touroot.member.fixture.MemberFixture;
import kr.touroot.member.helper.MemberTestHelper;
import kr.touroot.utils.DatabaseCleaner;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -75,7 +74,7 @@ void getByIdNotExist() {
@DisplayName("정상적인 값을 가진 요청이 주어지면 회원을 생성한다.")
@Test
void createMember() {
MemberRequest request = VALID_MEMBER.getRequest();
MemberRequest request = MemberFixture.TOUROOT_LOCAL_USER.getCreateRequest();

Long id = memberService.createMember(request);

Expand All @@ -94,11 +93,16 @@ void createMemberWithDuplicatedEmail() {
.hasMessage("이미 회원 가입되어 있는 이메일입니다.");
}

@DisplayName("중복된 이메일을 가진 회원을 생성하려하면 예외가 발생한다.")
@DisplayName("중복된 닉네임을 가진 회원을 생성하려하면 예외가 발생한다.")
Comment on lines -97 to +96

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저희 닉네임 중복도 제한되고 있었나요?
카카오톡 로그인이라 닉네임(이름)은 중복 허용해야하지 않나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 그러네요..

#616

이슈 생성했습니다~
간단한 작업인 듯 해서 해당 PR 머지되고 제가 작업 진행할게요~

@Test
void createMemberWithDuplicatedNickname() {
testHelper.persistMember();
MemberRequest request = DUPLICATE_NICKNAME_MEMBER.getRequest();
Member persistedMember = testHelper.persistMember();
String nonDuplicatedEmail = "noDuplicate" + persistedMember.getEmail();
String duplicatedNickname = persistedMember.getNickname();
MemberRequest request = MemberFixture.TOUROOT_LOCAL_USER.getCreateRequestWithEmailAndNickname(
nonDuplicatedEmail,
duplicatedNickname
);

assertThatThrownBy(() -> memberService.createMember(request))
.isInstanceOf(BadRequestException.class)
Expand Down
Loading
Loading