Skip to content

테스트 코드 개선 제안: 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);
}

// ... 매개변수 조합마다 새로운 메서드 필요
  1. 메서드 중복: 필요한 매개변수 조합마다 새로운 메서드를 생성해야 함
  2. 유지보수 어려움: 도메인 객체에 필드가 추가/변경될 때마다 모든 메서드 수정 필요
  3. 불필요한 의존성: Mock 객체 생성 시에도 Repository 의존성 필요
  4. 유연성 부족: 다양한 테스트 케이스에 맞는 객체 생성이 어려움

새로운 방식의 장점

  1. 빌더 패턴 + 명명된 파라미터 스타일: 코틀린과 유사한 방식으로 필요한 필드만 설정 가능

    Card card = mockCard.create()
        .id(1L)
        .nickname("테스트")
        .organization("회사")
        .build();
  2. 확장성 향상: 도메인 객체에 필드가 추가되어도 한 곳만 수정하면 됨

  3. 의존성 분리:

    • MockXXX: Repository 의존성 없이 테스트용 객체 생성
    • XXXFixture: Repository를 사용해 실제 DB에 저장
  4. 코드량 감소: 매개변수 조합마다 메서드를 생성할 필요 없음

  5. 명확한 의도: 객체 생성 코드에서 의도가 명확히 드러남

  6. 테스트 가독성 향상: 필요한 필드만 설정하므로 테스트 의도 파악이 쉬움

구현된 클래스

다음 클래스들이 구현되었습니다:

Mock 클래스 (테스트용 객체 생성)

  • MockUser: 사용자 Mock 객체 생성
  • MockCareer: 직업/직군 Mock 객체 생성
  • MockCard: 카드 Mock 객체 생성

Fixture 클래스 (실제 DB 저장)

  • UserFixture: 실제 DB에 사용자 저장
  • CareerFixture: 실제 DB에 직업/직군 저장
  • CardFixture: 실제 DB에 카드 저장

사용 방법

1. Mock 객체 생성 (단위 테스트용)

// 기본 초기화
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();

2. Fixture를 통한 실제 객체 저장 (통합 테스트용)

// 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();

3. 도우미 메서드 활용

// 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();

전체 코드

MockUser

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;
        }
    }
}

UserFixture

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);
        }
    }
}

MockCareer

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;
        }
    }
}

CareerFixture

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);
        }
    }
}

MockCard

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);
    }
}

CardFixture

결론

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 패턴은 테스트 코드 작성 시 다음과 같은 이점을 제공합니다:

  1. 코드 간결화: 필요한 필드만 설정하여 객체 생성 가능
  2. 의존성 분리: Mock과 Fixture 클래스 분리로 테스트 목적에 맞게 사용
  3. 유지보수성 향상: 도메인 모델 변경 시 한 곳만 수정하면 됨
  4. 확장성 개선: 새로운 필드 추가 시 기존 코드 변경 최소화
  5. 코드 가독성 향상: 객체 생성 의도가 명확히 드러남

이 패턴을 활용하면 테스트 코드 작성이 더 쉽고 효율적으로 이루어질 수 있으며, 코드의 품질과 유지보수성이 향상됩니다.

도메인 객체가 추가될 때도 같은 패턴으로 MockXXX와 XXXFixture 클래스를 구현하여 일관성 있게 테스트 코드를 작성할 수 있습니다.

Clone this wiki locally