-
Notifications
You must be signed in to change notification settings - Fork 1
테스트 코드 개선 제안: Mock과 Fixture 패턴
나경호(Na Gyeongho) edited this page May 27, 2025
·
1 revision
아래의 글을 보고 개선안을 작성했습니다.
[TestConfig 및 (이전) Fixture 활용 방법
테스트 코드 작성 시 반복적인 객체 생성 패턴을 개선하기 위해 Mock 클래스와 Fixture 클래스를 구현했습니다. 이 가이드는 개선된 테스트 코드 작성 방법과 장점을 설명합니다.
// 기존 방식 예시
public Card createCard(User user) {
return createCard(user, careerFixture.serverDeveloper(), DEFAULT_PREVIEW_INFO, DEFAULT_NICKNAME,
DEFAULT_IMAGE_PATH, DEFAULT_INTEREST_DOMAIN, DEFAULT_SUMMARY, DEFAULT_ORGANIZATION,
DEFAULT_SNS, DEFAULT_REGION, DEFAULT_HOBBY, DEFAULT_NEWS, DEFAULT_CONTENT, DEFAULT_PROJECT);
}
public Card createCard(User user, String nickname, PreviewInfoType previewInfoType) {
return createCard(user, careerFixture.serverDeveloper(), previewInfoType, nickname,
DEFAULT_IMAGE_PATH, DEFAULT_INTEREST_DOMAIN, DEFAULT_SUMMARY, DEFAULT_ORGANIZATION,
DEFAULT_SNS, DEFAULT_REGION, DEFAULT_HOBBY, DEFAULT_NEWS, DEFAULT_CONTENT, DEFAULT_PROJECT);
}
// ... 매개변수 조합마다 새로운 메서드 필요
- 메서드 중복: 필요한 매개변수 조합마다 새로운 메서드를 생성해야 함
- 유지보수 어려움: 도메인 객체에 필드가 추가/변경될 때마다 모든 메서드 수정 필요
- 불필요한 의존성: Mock 객체 생성 시에도 Repository 의존성 필요
- 유연성 부족: 다양한 테스트 케이스에 맞는 객체 생성이 어려움
-
빌더 패턴 + 명명된 파라미터 스타일: 코틀린과 유사한 방식으로 필요한 필드만 설정 가능
Card card = mockCard.create() .id(1L) .nickname("테스트") .organization("회사") .build();
-
확장성 향상: 도메인 객체에 필드가 추가되어도 한 곳만 수정하면 됨
-
의존성 분리:
-
MockXXX
: Repository 의존성 없이 테스트용 객체 생성 -
XXXFixture
: Repository를 사용해 실제 DB에 저장
-
-
코드량 감소: 매개변수 조합마다 메서드를 생성할 필요 없음
-
명확한 의도: 객체 생성 코드에서 의도가 명확히 드러남
-
테스트 가독성 향상: 필요한 필드만 설정하므로 테스트 의도 파악이 쉬움
다음 클래스들이 구현되었습니다:
-
MockUser
: 사용자 Mock 객체 생성 -
MockCareer
: 직업/직군 Mock 객체 생성 -
MockCard
: 카드 Mock 객체 생성
-
UserFixture
: 실제 DB에 사용자 저장 -
CareerFixture
: 실제 DB에 직업/직군 저장 -
CardFixture
: 실제 DB에 카드 저장
// 기본 초기화
MockUser mockUser = new MockUser();
MockCareer mockCareer = new MockCareer();
MockCard mockCard = new MockCard(mockCareer.serverDeveloper());
// 사용자 Mock 객체 생성
User user = mockUser.create()
.id(1L)
.name("테스트 사용자")
.email("[email protected]")
.googleOAuth("123456789")
.build();
// 직업/직군 Mock 객체 생성 (미리 정의된 객체 사용)
Career career = mockCareer.frontendDeveloper();
// 또는 커스텀 직업/직군 생성
Career customCareer = mockCareer.create()
.id(10L)
.job(Job.DEVELOPER)
.detailJobEn("DevOps Engineer")
.detailJobKr(List.of("데브옵스 엔지니어"))
.build();
// 카드 Mock 객체 생성
Card card = mockCard.create()
.id(1L)
.user(user)
.career(career)
.nickname("테스트 닉네임")
.previewInfo(PreviewInfoType.PROJECT)
.organization("테스트 회사")
.build();
// Repository 주입 필요
@Autowired
private UserRepository userRepository;
@Autowired
private CareerRepository careerRepository;
@Autowired
private CardRepository cardRepository;
// Fixture 초기화
UserFixture userFixture = new UserFixture(userRepository);
CareerFixture careerFixture = new CareerFixture(careerRepository);
// 사용자 생성 및 저장
User savedUser = userFixture.create()
.name("실제 사용자")
.email("[email protected]")
.googleOAuth("987654321")
.build();
// 직업/직군 생성 및 저장
Career savedCareer = careerFixture.uiuxDesigner();
// 카드 Fixture 초기화 (저장된 Career 객체 필요)
CardFixture cardFixture = new CardFixture(savedCareer, cardRepository);
// 카드 생성 및 저장
Card savedCard = cardFixture.create()
.user(savedUser)
.nickname("실제 닉네임")
.organization("실제 회사")
.build();
// SNS 생성 도우미 메서드
SNS githubSns = mockCard.createGithubSNS("https://github.com/username");
SNS linkedInSns = mockCard.createLinkedInSNS("https://linkedin.com/in/username");
// Content 생성 도우미 메서드
Content content = mockCard.createContent(
"테스트 컨텐츠",
"https://example.com/content",
"https://example.com/image.jpg",
"테스트 컨텐츠 설명");
// Project 생성 도우미 메서드
Project project = mockCard.createProject(
"테스트 프로젝트",
"https://example.com/project",
"https://example.com/project-image.jpg",
"테스트 프로젝트 설명");
// 모두 활용한 카드 생성
Card card = mockCard.create()
.user(user)
.addSns(githubSns)
.addSns(linkedInSns)
.addContent(content)
.addProject(project)
.build();
package com.evenly.took.feature.user.fixture;
import com.evenly.took.feature.auth.domain.OAuthIdentifier;
import com.evenly.took.feature.auth.domain.OAuthType;
import com.evenly.took.feature.user.domain.User;
import org.springframework.test.util.ReflectionTestUtils;
public class MockUser {
private static final Long DEFAULT_ID = 1L;
private static final String DEFAULT_NAME = "테스트 사용자";
private static final String DEFAULT_EMAIL = "[email protected]";
private static final OAuthIdentifier DEFAULT_OAUTH_IDENTIFIER =
OAuthIdentifier.builder()
.oauthId("12345")
.oauthType(OAuthType.GOOGLE)
.build();
// 모의 객체 생성을 위한 팩토리 메서드
public MockParams create() {
return new MockParams();
}
// 매개변수 객체 - Mock 객체 생성용
public class MockParams {
private Long id = DEFAULT_ID;
private String name = DEFAULT_NAME;
private String email = DEFAULT_EMAIL;
private OAuthIdentifier oauthIdentifier = DEFAULT_OAUTH_IDENTIFIER;
private MockParams() {
// 기본 생성자
}
// 명명된 매개변수 스타일의 메서드들
public MockParams id(Long id) {
this.id = id;
return this;
}
public MockParams name(String name) {
this.name = name;
return this;
}
public MockParams email(String email) {
this.email = email;
return this;
}
public MockParams oauthIdentifier(OAuthIdentifier oauthIdentifier) {
this.oauthIdentifier = oauthIdentifier;
return this;
}
public MockParams googleOAuth(String oauthId) {
this.oauthIdentifier = OAuthIdentifier.builder()
.oauthId(oauthId)
.oauthType(OAuthType.GOOGLE)
.build();
return this;
}
public MockParams kakaoOAuth(String oauthId) {
this.oauthIdentifier = OAuthIdentifier.builder()
.oauthId(oauthId)
.oauthType(OAuthType.KAKAO)
.build();
return this;
}
public MockParams appleOAuth(String oauthId) {
this.oauthIdentifier = OAuthIdentifier.builder()
.oauthId(oauthId)
.oauthType(OAuthType.APPLE)
.build();
return this;
}
// 최종 Mock 객체 생성
public User build() {
User user = User.builder()
.oauthIdentifier(oauthIdentifier)
.name(name)
.email(email)
.build();
ReflectionTestUtils.setField(user, "id", id);
return user;
}
}
}
package com.evenly.took.feature.user.fixture;
import com.evenly.took.feature.auth.domain.OAuthIdentifier;
import com.evenly.took.feature.auth.domain.OAuthType;
import com.evenly.took.feature.user.domain.User;
import com.evenly.took.feature.user.repository.UserRepository;
public class UserFixture {
private static final String DEFAULT_NAME = "테스트 사용자";
private static final String DEFAULT_EMAIL = "[email protected]";
private static final OAuthIdentifier DEFAULT_OAUTH_IDENTIFIER =
OAuthIdentifier.builder()
.oauthId("12345")
.oauthType(OAuthType.GOOGLE)
.build();
private final UserRepository userRepository;
public UserFixture(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 실제 저장 객체 생성을 위한 팩토리 메서드
public UserParams create() {
return new UserParams();
}
// 매개변수 객체 - 실제 DB 저장용
public class UserParams {
private String name = DEFAULT_NAME;
private String email = DEFAULT_EMAIL;
private OAuthIdentifier oauthIdentifier = DEFAULT_OAUTH_IDENTIFIER;
private UserParams() {
// 기본 생성자
}
// 명명된 매개변수 스타일의 메서드들
public UserParams name(String name) {
this.name = name;
return this;
}
public UserParams email(String email) {
this.email = email;
return this;
}
public UserParams oauthIdentifier(OAuthIdentifier oauthIdentifier) {
this.oauthIdentifier = oauthIdentifier;
return this;
}
public UserParams googleOAuth(String oauthId) {
this.oauthIdentifier = OAuthIdentifier.builder()
.oauthId(oauthId)
.oauthType(OAuthType.GOOGLE)
.build();
return this;
}
public UserParams kakaoOAuth(String oauthId) {
this.oauthIdentifier = OAuthIdentifier.builder()
.oauthId(oauthId)
.oauthType(OAuthType.KAKAO)
.build();
return this;
}
public UserParams appleOAuth(String oauthId) {
this.oauthIdentifier = OAuthIdentifier.builder()
.oauthId(oauthId)
.oauthType(OAuthType.APPLE)
.build();
return this;
}
// 최종 객체 생성 및 저장
public User build() {
User user = User.builder()
.oauthIdentifier(oauthIdentifier)
.name(name)
.email(email)
.build();
return userRepository.save(user);
}
}
}
package com.evenly.took.feature.card.fixture;
import com.evenly.took.feature.card.domain.Career;
import com.evenly.took.feature.card.domain.Job;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Collections;
import java.util.List;
public class MockCareer {
private static final Long DEFAULT_ID = 1L;
// 미리 정의된 Career 인스턴스 제공 메서드
public Career serverDeveloper() {
return create()
.id(1L)
.job(Job.DEVELOPER)
.detailJobEn("Backend Developer")
.detailJobKr(List.of("백엔드 개발자", "서버 개발자"))
.build();
}
public Career frontendDeveloper() {
return create()
.id(2L)
.job(Job.DEVELOPER)
.detailJobEn("Frontend Developer")
.detailJobKr(List.of("프론트엔드 개발자", "웹 개발자"))
.build();
}
public Career mobileDeveloper() {
return create()
.id(3L)
.job(Job.DEVELOPER)
.detailJobEn("Mobile Developer")
.detailJobKr(List.of("모바일 개발자", "앱 개발자"))
.build();
}
public Career uiuxDesigner() {
return create()
.id(4L)
.job(Job.DESIGNER)
.detailJobEn("UI/UX Designer")
.detailJobKr(List.of("UI/UX 디자이너", "제품 디자이너"))
.build();
}
public Career graphicDesigner() {
return create()
.id(5L)
.job(Job.DESIGNER)
.detailJobEn("Graphic Designer")
.detailJobKr(List.of("그래픽 디자이너", "시각 디자이너"))
.build();
}
// 모의 객체 생성을 위한 팩토리 메서드
public MockParams create() {
return new MockParams();
}
// 매개변수 객체 - Mock 객체 생성용
public class MockParams {
private Long id = DEFAULT_ID;
private Job job = Job.DEVELOPER;
private String detailJobEn = "Default Job";
private List<String> detailJobKr = Collections.singletonList("기본 직업");
private MockParams() {
// 기본 생성자
}
// 명명된 매개변수 스타일의 메서드들
public MockParams id(Long id) {
this.id = id;
return this;
}
public MockParams job(Job job) {
this.job = job;
return this;
}
public MockParams detailJobEn(String detailJobEn) {
this.detailJobEn = detailJobEn;
return this;
}
public MockParams detailJobKr(List<String> detailJobKr) {
this.detailJobKr = detailJobKr;
return this;
}
// 최종 Mock 객체 생성
public Career build() {
Career career = Career.builder()
.job(job)
.detailJobEn(detailJobEn)
.detailJobKr(detailJobKr)
.build();
ReflectionTestUtils.setField(career, "id", id);
return career;
}
}
}
package com.evenly.took.feature.card.fixture;
import com.evenly.took.feature.card.domain.Career;
import com.evenly.took.feature.card.domain.Job;
import com.evenly.took.feature.card.repository.CareerRepository;
import java.util.Collections;
import java.util.List;
public class CareerFixture {
private final CareerRepository careerRepository;
public CareerFixture(CareerRepository careerRepository) {
this.careerRepository = careerRepository;
}
// 미리 정의된 Career 인스턴스 제공 메서드
public Career serverDeveloper() {
return create()
.job(Job.DEVELOPER)
.detailJobEn("Backend Developer")
.detailJobKr(List.of("백엔드 개발자", "서버 개발자"))
.build();
}
public Career frontendDeveloper() {
return create()
.job(Job.DEVELOPER)
.detailJobEn("Frontend Developer")
.detailJobKr(List.of("프론트엔드 개발자", "웹 개발자"))
.build();
}
public Career mobileDeveloper() {
return create()
.job(Job.DEVELOPER)
.detailJobEn("Mobile Developer")
.detailJobKr(List.of("모바일 개발자", "앱 개발자"))
.build();
}
public Career uiuxDesigner() {
return create()
.job(Job.DESIGNER)
.detailJobEn("UI/UX Designer")
.detailJobKr(List.of("UI/UX 디자이너", "제품 디자이너"))
.build();
}
public Career graphicDesigner() {
return create()
.job(Job.DESIGNER)
.detailJobEn("Graphic Designer")
.detailJobKr(List.of("그래픽 디자이너", "시각 디자이너"))
.build();
}
// 실제 저장 객체 생성을 위한 팩토리 메서드
public CareerParams create() {
return new CareerParams();
}
// 매개변수 객체 - 실제 DB 저장용
public class CareerParams {
private Job job = Job.DEVELOPER;
private String detailJobEn = "Default Job";
private List<String> detailJobKr = Collections.singletonList("기본 직업");
private CareerParams() {
// 기본 생성자
}
// 명명된 매개변수 스타일의 메서드들
public CareerParams job(Job job) {
this.job = job;
return this;
}
public CareerParams detailJobEn(String detailJobEn) {
this.detailJobEn = detailJobEn;
return this;
}
public CareerParams detailJobKr(List<String> detailJobKr) {
this.detailJobKr = detailJobKr;
return this;
}
// 최종 객체 생성 및 저장
public Career build() {
Career career = Career.builder()
.job(job)
.detailJobEn(detailJobEn)
.detailJobKr(detailJobKr)
.build();
return careerRepository.save(career);
}
}
}
package com.evenly.took.feature.card.fixture;
import com.evenly.took.feature.card.domain.Card;
import com.evenly.took.feature.card.domain.Career;
import com.evenly.took.feature.card.domain.PreviewInfoType;
import com.evenly.took.feature.card.domain.vo.Content;
import com.evenly.took.feature.card.domain.vo.Project;
import com.evenly.took.feature.card.domain.vo.SNS;
import com.evenly.took.feature.card.domain.SNSType;
import com.evenly.took.feature.user.domain.User;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MockCard {
private static final Long DEFAULT_ID = 1L;
private static final PreviewInfoType DEFAULT_PREVIEW_INFO = PreviewInfoType.CAREER;
private static final String DEFAULT_NICKNAME = "기본 닉네임";
private static final String DEFAULT_IMAGE_PATH = "default/image/path.jpg";
private static final List<String> DEFAULT_INTEREST_DOMAIN = List.of("개발", "서버");
private static final String DEFAULT_SUMMARY = "기본 요약 정보";
private static final String DEFAULT_ORGANIZATION = "기본 조직";
private static final String DEFAULT_REGION = "서울";
private static final String DEFAULT_HOBBY = "코딩";
private static final String DEFAULT_NEWS = "최근 기본 소식";
private static final List<SNS> DEFAULT_SNS = Collections.emptyList();
private static final List<Content> DEFAULT_CONTENT = Collections.emptyList();
private static final List<Project> DEFAULT_PROJECT = Collections.emptyList();
private final Career career;
public MockCard(Career career) {
this.career = career;
}
// 모의 객체 생성을 위한 팩토리 메서드
public MockParams create() {
return new MockParams();
}
// 매개변수 객체 - Mock 객체 생성용
public class MockParams {
private Long id = DEFAULT_ID;
private User user = null;
private Career career = MockCard.this.career;
private PreviewInfoType previewInfo = DEFAULT_PREVIEW_INFO;
private String nickname = DEFAULT_NICKNAME;
private String imagePath = DEFAULT_IMAGE_PATH;
private List<String> interestDomain = DEFAULT_INTEREST_DOMAIN;
private String summary = DEFAULT_SUMMARY;
private String organization = DEFAULT_ORGANIZATION;
private String region = DEFAULT_REGION;
private String hobby = DEFAULT_HOBBY;
private String news = DEFAULT_NEWS;
private List<SNS> sns = new ArrayList<>(DEFAULT_SNS);
private List<Content> content = new ArrayList<>(DEFAULT_CONTENT);
private List<Project> project = new ArrayList<>(DEFAULT_PROJECT);
private MockParams() {
// 기본 생성자
}
// 명명된 매개변수 스타일의 메서드들
public MockParams id(Long id) {
this.id = id;
return this;
}
public MockParams user(User user) {
this.user = user;
return this;
}
public MockParams career(Career career) {
this.career = career;
return this;
}
public MockParams previewInfo(PreviewInfoType previewInfo) {
this.previewInfo = previewInfo;
return this;
}
public MockParams nickname(String nickname) {
this.nickname = nickname;
return this;
}
public MockParams imagePath(String imagePath) {
this.imagePath = imagePath;
return this;
}
public MockParams interestDomain(List<String> interestDomain) {
this.interestDomain = interestDomain;
return this;
}
public MockParams summary(String summary) {
this.summary = summary;
return this;
}
public MockParams organization(String organization) {
this.organization = organization;
return this;
}
public MockParams region(String region) {
this.region = region;
return this;
}
public MockParams hobby(String hobby) {
this.hobby = hobby;
return this;
}
public MockParams news(String news) {
this.news = news;
return this;
}
public MockParams sns(List<SNS> sns) {
this.sns = new ArrayList<>(sns);
return this;
}
public MockParams addSns(SNS sns) {
this.sns.add(sns);
return this;
}
public MockParams content(List<Content> content) {
this.content = new ArrayList<>(content);
return this;
}
public MockParams addContent(Content content) {
this.content.add(content);
return this;
}
public MockParams project(List<Project> project) {
this.project = new ArrayList<>(project);
return this;
}
public MockParams addProject(Project project) {
this.project.add(project);
return this;
}
// 최종 Mock 객체 생성
public Card build() {
if (user == null) {
throw new IllegalArgumentException("User must be provided");
}
Card card = Card.builder()
.user(user)
.career(career)
.previewInfo(previewInfo)
.nickname(nickname)
.imagePath(imagePath)
.interestDomain(interestDomain)
.summary(summary)
.organization(organization)
.region(region)
.hobby(hobby)
.news(news)
.sns(sns)
.content(content)
.project(project)
.build();
ReflectionTestUtils.setField(card, "id", id);
return card;
}
}
// 편의를 위한 도우미 메서드들 - 기본 객체 생성
public SNS createGithubSNS(String link) {
return new SNS(SNSType.GITHUB, link);
}
public SNS createLinkedInSNS(String link) {
return new SNS(SNSType.LINKEDIN, link);
}
public Content createContent(String title, String link, String imageUrl, String description) {
return new Content(title, link, imageUrl, description);
}
public Project createProject(String title, String link, String imageUrl, String description) {
return new Project(title, link, imageUrl, description);
}
}
package com.evenly.took.feature.card.fixture;
import com.evenly.took.feature.card.domain.Card;
import com.evenly.took.feature.card.domain.Career;
import com.evenly.took.feature.card.domain.PreviewInfoType;
import com.evenly.took.feature.card.domain.SNSType;
import com.evenly.took.feature.card.domain.vo.Content;
import com.evenly.took.feature.card.domain.vo.Project;
import com.evenly.took.feature.card.domain.vo.SNS;
import com.evenly.took.feature.card.repository.CardRepository;
import com.evenly.took.feature.user.domain.User;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CardFixture {
private static final PreviewInfoType DEFAULT_PREVIEW_INFO = PreviewInfoType.CAREER;
private static final String DEFAULT_NICKNAME = "기본 닉네임";
private static final String DEFAULT_IMAGE_PATH = "default/image/path.jpg";
private static final List<String> DEFAULT_INTEREST_DOMAIN = List.of("개발", "서버");
private static final String DEFAULT_SUMMARY = "기본 요약 정보";
private static final String DEFAULT_ORGANIZATION = "기본 조직";
private static final String DEFAULT_REGION = "서울";
private static final String DEFAULT_HOBBY = "코딩";
private static final String DEFAULT_NEWS = "최근 기본 소식";
private static final List<SNS> DEFAULT_SNS = Collections.emptyList();
private static final List<Content> DEFAULT_CONTENT = Collections.emptyList();
private static final List<Project> DEFAULT_PROJECT = Collections.emptyList();
private final Career career;
private final CardRepository cardRepository;
public CardFixture(Career career, CardRepository cardRepository) {
this.career = career;
this.cardRepository = cardRepository;
}
// 실제 저장 객체 생성을 위한 팩토리 메서드
public CardParams create() {
return new CardParams();
}
// 매개변수 객체 - 실제 DB 저장용
public class CardParams {
private User user = null;
private Career career = CardFixture.this.career;
private PreviewInfoType previewInfo = DEFAULT_PREVIEW_INFO;
private String nickname = DEFAULT_NICKNAME;
private String imagePath = DEFAULT_IMAGE_PATH;
private List<String> interestDomain = DEFAULT_INTEREST_DOMAIN;
private String summary = DEFAULT_SUMMARY;
private String organization = DEFAULT_ORGANIZATION;
private String region = DEFAULT_REGION;
private String hobby = DEFAULT_HOBBY;
private String news = DEFAULT_NEWS;
private List<SNS> sns = new ArrayList<>(DEFAULT_SNS);
private List<Content> content = new ArrayList<>(DEFAULT_CONTENT);
private List<Project> project = new ArrayList<>(DEFAULT_PROJECT);
private CardParams() {
// 기본 생성자
}
// 명명된 매개변수 스타일의 메서드들
public CardParams user(User user) {
this.user = user;
return this;
}
public CardParams career(Career career) {
this.career = career;
return this;
}
public CardParams previewInfo(PreviewInfoType previewInfo) {
this.previewInfo = previewInfo;
return this;
}
public CardParams nickname(String nickname) {
this.nickname = nickname;
return this;
}
public CardParams imagePath(String imagePath) {
this.imagePath = imagePath;
return this;
}
public CardParams interestDomain(List<String> interestDomain) {
this.interestDomain = interestDomain;
return this;
}
public CardParams summary(String summary) {
this.summary = summary;
return this;
}
public CardParams organization(String organization) {
this.organization = organization;
return this;
}
public CardParams region(String region) {
this.region = region;
return this;
}
public CardParams hobby(String hobby) {
this.hobby = hobby;
return this;
}
public CardParams news(String news) {
this.news = news;
return this;
}
public CardParams sns(List<SNS> sns) {
this.sns = new ArrayList<>(sns);
return this;
}
public CardParams addSns(SNS sns) {
this.sns.add(sns);
return this;
}
public CardParams content(List<Content> content) {
this.content = new ArrayList<>(content);
return this;
}
public CardParams addContent(Content content) {
this.content.add(content);
return this;
}
public CardParams project(List<Project> project) {
this.project = new ArrayList<>(project);
return this;
}
public CardParams addProject(Project project) {
this.project.add(project);
return this;
}
// 최종 객체 생성 및 저장
public Card build() {
if (user == null) {
throw new IllegalArgumentException("User must be provided for real cards");
}
Card card = Card.builder()
.user(user)
.career(career)
.previewInfo(previewInfo)
.nickname(nickname)
.imagePath(imagePath)
.interestDomain(interestDomain)
.summary(summary)
.organization(organization)
.region(region)
.hobby(hobby)
.news(news)
.sns(sns)
.content(content)
.project(project)
.build();
return cardRepository.save(card);
}
}
// 편의를 위한 도우미 메서드들 - 기본 객체 생성
public SNS createGithubSNS(String link) {
return new SNS(SNSType.GITHUB, link);
}
public SNS createLinkedInSNS(String link) {
return new SNS(SNSType.LINKEDIN, link);
}
public Content createContent(String title, String link, String imageUrl, String description) {
return new Content(title, link, imageUrl, description);
}
public Project createProject(String title, String link, String imageUrl, String description) {
return new Project(title, link, imageUrl, description);
}
}
새로운 Mock/Fixture 패턴은 테스트 코드 작성 시 다음과 같은 이점을 제공합니다:
- 코드 간결화: 필요한 필드만 설정하여 객체 생성 가능
- 의존성 분리: Mock과 Fixture 클래스 분리로 테스트 목적에 맞게 사용
- 유지보수성 향상: 도메인 모델 변경 시 한 곳만 수정하면 됨
- 확장성 개선: 새로운 필드 추가 시 기존 코드 변경 최소화
- 코드 가독성 향상: 객체 생성 의도가 명확히 드러남
이 패턴을 활용하면 테스트 코드 작성이 더 쉽고 효율적으로 이루어질 수 있으며, 코드의 품질과 유지보수성이 향상됩니다.
도메인 객체가 추가될 때도 같은 패턴으로 MockXXX와 XXXFixture 클래스를 구현하여 일관성 있게 테스트 코드를 작성할 수 있습니다.
- 백엔드 컨벤션
- TestContainer 적용 이유
- DB Read replica 구성 (AWS RDS)
- RDS 프라이빗 설정 후 로컬 터널링
- 공통 성공/에러 응답 정의
- 테스트 환경 통합 작업
- 소셜 로그인 설계(Spring Security 없이 OAuth2.0)
- 클라우드 환경변수 관리 AWS SSM Parameter Store
- 코드단 제안 사항 (카카오톡 내용 요약)
- Spring에서 JWT 인증/인가 위치 제안(Filter와 Interceptor)
- 알림 아키텍처 정리
- 외부 API Call 및 무거운 작업 Serverless Lambda 로 분리
- Redis GEO (유저 위치 저장 및 검색)
- 푸시알림 권한 받는 시점
- TestConfig 및 (이전) Fixture 활용 방법
- 테스트 코드 개선 제안: Mock과 Fixture 패턴
- 테스트 객체 자동 생성
- 푸시알림 설계
- Fixture 및 Factory 활용 방법
- 로그 모니터링 시스템 구축
- 성능테스트 케이스