커스터마이징 API

이 문서에서 배우는 내용

  • 테스트에 필요한 데이터를 쉽게 만드는 방법
  • 원하는 값을 가진 객체를 자유롭게 생성하는 방법
  • 실제 테스트에서 자주 필요한 데이터 생성 방법

시작하기 전에

이 문서에서는 테스트 데이터를 쉽게 만들 수 있는 다양한 방법을 배웁니다. 예를 들어 다음과 같은 상황에서 Fixture Monkey API를 활용할 수 있습니다:

  • 회원가입 테스트를 위해 특정 나이대의 회원 데이터가 필요할 때
  • 주문 테스트를 위해 여러 개의 상품이 담긴 장바구니가 필요할 때
  • 결제 테스트를 위해 특정 금액 이상의 주문이 필요할 때

알아두면 좋은 용어

  • 샘플링(sampling): 테스트용 데이터를 실제로 만드는 것을 의미합니다. sample() 메서드를 호출할 때마다 새로운 테스트 데이터가 생성됩니다.
  • 빌더(builder): 객체를 단계적으로 만들 수 있게 도와주는 도구입니다. Fixture Monkey에서는 giveMeBuilder()로 빌더를 생성합니다.
  • Path Expression: 객체의 어떤 속성을 변경할지 지정하는 방법입니다. 예를 들어 “age"는 나이 속성을, “items[0]“은 리스트의 첫 번째 아이템을, “address.city"는 주소 객체 안의 도시 속성을 의미합니다.

목차

API 요약 표

기본 API (처음 사용하시는 분들을 위한 필수 API)

API설명예시 상황
set()원하는 값 직접 지정하기회원의 나이를 20살로 지정
size()리스트 크기 지정하기장바구니에 상품 3개 담기
setNull()null 값 지정하기탈퇴한 회원의 이메일을 null로 설정

활용 API (기본 기능에 익숙해진 후 사용하세요)

API설명예시 상황
setInner()재사용 가능한 설정 만들기여러 테스트에서 같은 형태의 회원정보 사용
setLazy()동적으로 값 생성하기순차적인 주문번호 생성
setPostCondition()조건에 맞는 값 만들기성인만 가입 가능한 서비스 테스트
fixed()항상 같은 값 생성하기테스트마다 동일한 테스트 데이터 사용
limit일부만 값 설정하기장바구니의 일부 상품만 할인 적용

고급 API (복잡한 테스트 상황에서 사용하세요)

API설명예시 상황
thenApply()연관된 값 설정하기주문 총액을 주문 상품 가격의 합으로 설정

기본 API 사용하기

set()

set() 메서드는 객체의 특정 속성에 원하는 값을 설정할 때 사용합니다. 가장 기본적이고 많이 사용되는 API입니다.

기본 사용법

// 회원 데이터 생성 예제
Member member = fixtureMonkey.giveMeBuilder(Member.class)
    .set("name", "홍길동")        // 이름 설정
    .set("age", 25)             // 나이 설정
    .set("email", "hong@test.com") // 이메일 설정
    .sample();

// 주문 데이터 생성 예제
Order order = fixtureMonkey.giveMeBuilder(Order.class)
    .set("orderId", "ORDER-001")           // 주문번호 설정
    .set("totalAmount", BigDecimal.valueOf(15000)) // 주문금액 설정
    .sample();
// 회원 데이터 생성 예제
val member = fixtureMonkey.giveMeBuilder<Member>()
    .setExp(Member::name, "홍길동")        // 이름 설정
    .setExp(Member::age, 25)             // 나이 설정
    .setExp(Member::email, "hong@test.com") // 이메일 설정
    .sample()

// 주문 데이터 생성 예제
val order = fixtureMonkey.giveMeBuilder<Order>()
    .setExp(Order::orderId, "ORDER-001")           // 주문번호 설정
    .setExp(Order::totalAmount, BigDecimal.valueOf(15000)) // 주문금액 설정
    .sample()

size(), minSize(), maxSize()

size() 메서드는 리스트나 배열같은 컬렉션의 크기를 지정할 때 사용합니다. 정확한 크기를 설정하거나, 최소/최대 크기를 지정할 수 있습니다.

기본 사용법

// 장바구니에 상품 3개 담기
Cart cart = fixtureMonkey.giveMeBuilder(Cart.class)
    .size("items", 3)  // 장바구니에 3개 상품
    .sample();

// 2~4개 사이의 리뷰가 있는 상품 만들기
Product product = fixtureMonkey.giveMeBuilder(Product.class)
    .size("reviews", 2, 4)  // 최소 2개, 최대 4개 리뷰
    .sample();
// 장바구니에 상품 3개 담기
val cart = fixtureMonkey.giveMeBuilder<Cart>()
    .sizeExp(Cart::items, 3)  // 장바구니에 3개 상품
    .sample()

// 2~4개 사이의 리뷰가 있는 상품 만들기
val product = fixtureMonkey.giveMeBuilder<Product>()
    .sizeExp(Product::reviews, 2, 4)  // 최소 2개, 최대 4개 리뷰
    .sample()

setNull(), setNotNull()

setNull()setNotNull()은 특정 속성을 null로 만들거나, 반드시 값이 있도록 만들 때 사용합니다.

기본 사용법

// 탈퇴한 회원 데이터 생성 (이메일은 null)
Member withdrawnMember = fixtureMonkey.giveMeBuilder(Member.class)
    .set("name", "홍길동")
    .setNull("email")      // 이메일은 null로 설정
    .sample();

// 필수 입력 정보가 있는 주문 생성
Order validOrder = fixtureMonkey.giveMeBuilder(Order.class)
    .setNotNull("orderId")     // 주문번호는 반드시 있어야 함
    .setNotNull("orderDate")   // 주문일자도 반드시 있어야 함
    .sample();
// 탈퇴한 회원 데이터 생성 (이메일은 null)
val withdrawnMember = fixtureMonkey.giveMeBuilder<Member>()
    .setExp(Member::name, "홍길동")
    .setNullExp(Member::email)      // 이메일은 null로 설정
    .sample()

// 필수 입력 정보가 있는 주문 생성
val validOrder = fixtureMonkey.giveMeBuilder<Order>()
    .setNotNullExp(Order::orderId)     // 주문번호는 반드시 있어야 함
    .setNotNullExp(Order::orderDate)   // 주문일자도 반드시 있어야 함
    .sample()

활용 API 배우기

setInner()

setInner()는 여러 테스트에서 재사용할 수 있는 설정을 만들 때 사용합니다. 예를 들어, 여러 테스트에서 동일한 형태의 회원 정보나 주문 정보가 필요할 때 유용합니다.

기본 사용법

// VIP 회원 정보 설정
InnerSpec vipMemberSpec = new InnerSpec()
    .property("grade", "VIP")
    .property("point", 10000)
    .property("joinDate", LocalDate.now().minusYears(1));

// VIP 회원 생성에 재사용
Member vipMember = fixtureMonkey.giveMeBuilder(Member.class)
    .setInner(vipMemberSpec)
    .sample();
// VIP 회원 정보 설정
val vipMemberSpec = InnerSpec()
    .property("grade", "VIP")
    .property("point", 10000)
    .property("joinDate", LocalDate.now().minusYears(1))

// VIP 회원 생성에 재사용
val vipMember = fixtureMonkey.giveMeBuilder<Member>()
    .setInner(vipMemberSpec)
    .sample()

setLazy()

setLazy()는 매번 다른 값이나 순차적인 값을 생성할 때 사용합니다. 예를 들어, 순차적인 주문번호나 현재 시간을 사용할 때 유용합니다.

기본 사용법

// 순차적인 주문번호 생성
AtomicInteger orderCounter = new AtomicInteger(1);
Order order = fixtureMonkey.giveMeBuilder(Order.class)
    .setLazy("orderId", () -> "ORDER-" + orderCounter.getAndIncrement())
    .sample();  // ORDER-1

Order nextOrder = fixtureMonkey.giveMeBuilder(Order.class)
    .setLazy("orderId", () -> "ORDER-" + orderCounter.getAndIncrement())
    .sample();  // ORDER-2
// 순차적인 주문번호 생성
var orderCounter = AtomicInteger(1)
val order = fixtureMonkey.giveMeBuilder<Order>()
    .setLazy("orderId") { "ORDER-${orderCounter.getAndIncrement()}" }
    .sample()  // ORDER-1

val nextOrder = fixtureMonkey.giveMeBuilder<Order>()
    .setLazy("orderId") { "ORDER-${orderCounter.getAndIncrement()}" }
    .sample()  // ORDER-2

setPostCondition()

setPostCondition()은 특정 조건을 만족하는 값을 생성할 때 사용합니다. 예를 들어, 성인 회원만 가입 가능한 서비스를 테스트할 때 유용합니다.

기본 사용법

// 성인 회원만 생성
Member adultMember = fixtureMonkey.giveMeBuilder(Member.class)
    .setPostCondition("age", Integer.class, age -> age >= 19)
    .sample();

// 10만원 이상의 주문만 생성
Order largeOrder = fixtureMonkey.giveMeBuilder(Order.class)
    .setPostCondition("totalAmount", BigDecimal.class, 
        amount -> amount.compareTo(BigDecimal.valueOf(100000)) >= 0)
    .sample();
// 성인 회원만 생성
val adultMember = fixtureMonkey.giveMeBuilder<Member>()
    .setPostConditionExp(Member::age, Int::class.java) { it >= 19 }
    .sample()

// 10만원 이상의 주문만 생성
val largeOrder = fixtureMonkey.giveMeBuilder<Order>()
    .setPostConditionExp(Order::totalAmount, BigDecimal::class.java) { 
        it >= BigDecimal.valueOf(100000) 
    }
    .sample()

fixed()

fixed()는 테스트를 실행할 때마다 동일한 테스트 데이터가 필요할 때 사용합니다.

기본 사용법

// 항상 동일한 회원 정보로 테스트
Member member = fixtureMonkey.giveMeBuilder(Member.class)
    .set("name", "홍길동")
    .set("age", 30)
    .fixed()  // 항상 동일한 데이터 생성
    .sample();
// 항상 동일한 회원 정보로 테스트
val member = fixtureMonkey.giveMeBuilder<Member>()
    .setExp(Member::name, "홍길동")
    .setExp(Member::age, 30)
    .fixed()  // 항상 동일한 데이터 생성
    .sample()

limit

limit는 컬렉션의 일부 요소만 특정 값으로 설정하고 싶을 때 사용합니다.

기본 사용법

// 장바구니의 일부 상품만 할인 적용
Cart cart = fixtureMonkey.giveMeBuilder(Cart.class)
    .size("items", 5)                    // 5개 상품
    .set("items[*].onSale", true, 2)    // 2개 상품만 할인
    .sample();
// 장바구니의 일부 상품만 할인 적용
val cart = fixtureMonkey.giveMeBuilder<Cart>()
    .sizeExp(Cart::items, 5)                    // 5개 상품
    .set("items[*].onSale", true, 2)    // 2개 상품만 할인
    .sample()

고급 API 활용하기

thenApply()

thenApply()는 이미 생성된 객체의 값을 기반으로 다른 값을 설정해야 할 때 사용합니다. 예를 들어, 주문의 총액을 주문 상품들의 가격 합계로 설정할 때 유용합니다.

기본 사용법

// 주문 상품 가격의 합계로 총액 설정
Order order = fixtureMonkey.giveMeBuilder(Order.class)
    .size("items", 3)  // 3개 상품
    .thenApply((tempOrder, orderBuilder) -> {
        // 총액 계산
        BigDecimal total = tempOrder.getItems().stream()
            .map(item -> item.getPrice())
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 계산된 총액 설정
        orderBuilder.set("totalAmount", total);
    })
    .sample();
// 주문 상품 가격의 합계로 총액 설정
val order = fixtureMonkey.giveMeBuilder<Order>()
    .sizeExp(Order::items, 3)  // 3개 상품
    .thenApply { tempOrder, orderBuilder ->
        // 총액 계산
        val total = tempOrder.items
            .map { it.price }
            .fold(BigDecimal.ZERO, BigDecimal::add)
        // 계산된 총액 설정
        orderBuilder.set("totalAmount", total)
    }
    .sample()

자주 묻는 질문 (FAQ)

Q: 어떤 API부터 배워야 하나요?

처음에는 다음 순서로 배우시는 것을 추천합니다:

  1. set() - 가장 기본적이고 많이 사용되는 API입니다.
  2. size() - 리스트나 배열을 다룰 때 필요합니다.
  3. setNull(), setNotNull() - null 값을 다룰 때 사용합니다.

이후 테스트 작성에 익숙해지면 다른 API들을 하나씩 배워가시면 됩니다.

Q: 테스트마다 같은 데이터가 필요하면 어떻게 하나요?

fixed()를 사용하면 됩니다. 예를 들어:

// 테스트마다 동일한 회원 정보 사용
ArbitraryBuilder<Member> memberBuilder = fixtureMonkey.giveMeBuilder(Member.class)
    .set("name", "홍길동")
    .set("age", 30)
    .fixed();  // 항상 동일한 데이터 생성

Member member1 = memberBuilder.sample(); // 항상 같은 데이터
Member member2 = memberBuilder.sample(); // member1과 동일

Q: 실수로 잘못된 값이 생성되는 것을 방지하려면 어떻게 하나요?

setPostCondition()을 사용하여 값의 범위나 조건을 지정할 수 있습니다:

// 나이는 반드시 1-100 사이
Member member = fixtureMonkey.giveMeBuilder(Member.class)
    .setPostCondition("age", Integer.class, age -> age >= 1 && age <= 100)
    .sample();