옵션 개요
Fixture Monkey를 처음 사용할 때는 다양한 옵션들이 복잡하게 느껴질 수 있습니다. 이 가이드는 옵션들을 이해하고 어디서부터 시작해야 할지 파악하는 데 도움을 줄 것입니다.
옵션 vs ArbitraryBuilder API
Fixture Monkey에서 테스트 데이터를 설정하는 방법은 크게 두 가지가 있습니다:
옵션 (Options)
- FixtureMonkey 인스턴스 생성 시 설정
- 모든 테스트 데이터 생성에 적용되는 전역 규칙 정의
- 재사용 가능한 설정
ArbitraryBuilder API
- 개별 테스트 데이터 생성 시 설정
- 특정 테스트 케이스에 필요한 일회성 설정
- 더 세밀한 제어 가능
예시:
// 옵션 사용 - 모든 Product 인스턴스에 적용
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultNotNull(true) // 모든 필드를 non-null로 설정
.register(Product.class, fm -> fm.giveMeBuilder(Product.class)
.size("items", 3)) // items는 항상 3개
.build();
// ArbitraryBuilder API 사용 - 특정 테스트에만 적용
Product specificProduct = fixtureMonkey.giveMeBuilder(Product.class)
.set("name", "테스트 상품") // 이 테스트에서만 이름 설정
.set("price", 1000) // 이 테스트에서만 가격 설정
.sample();
왜 옵션을 사용해야 하나요?
옵션을 사용해야 하는 중요한 이유들이 있습니다:
1. 테스트 데이터의 일관성
- 문제: 여러 테스트에서 동일한 규칙을 적용해야 함
// 옵션 없이 - 매 테스트마다 반복 설정 필요 Product product1 = fixtureMonkey.giveMeBuilder(Product.class) .set("price", Arbitraries.longs().greaterThan(0)) .sample(); Product product2 = fixtureMonkey.giveMeBuilder(Product.class) .set("price", Arbitraries.longs().greaterThan(0)) .sample();
// 옵션 사용 - 한 번 설정으로 모든 테스트에 적용 FixtureMonkey fixtureMonkey = FixtureMonkey.builder() .register(Product.class, fm -> fm.giveMeBuilder(Product.class) .set("price", Arbitraries.longs().greaterThan(0))) .build(); Product product1 = fixtureMonkey.giveMeOne(Product.class); // 자동으로 양수 가격 Product product2 = fixtureMonkey.giveMeOne(Product.class); // 자동으로 양수 가격
2. 테스트 유지보수성
- 문제: 규칙이 변경될 때 모든 테스트를 수정해야 함
// 옵션 사용 - 한 곳에서 규칙 관리 public class TestConfig { public static FixtureMonkey createFixtureMonkey() { return FixtureMonkey.builder() .defaultNotNull(true) .register(Product.class, fm -> fm.giveMeBuilder(Product.class) .set("price", Arbitraries.longs().greaterThan(0)) .set("stock", Arbitraries.integers().greaterThan(0))) .register(Order.class, fm -> fm.giveMeBuilder(Order.class) // 여기에 orderRules()에 해당하는 빌더 체이닝 추가 ) .build(); } }
3. 도메인 규칙 적용
- 문제: 비즈니스 규칙을 테스트 데이터에도 적용해야 함
// 옵션을 통한 도메인 규칙 적용 FixtureMonkey fixtureMonkey = FixtureMonkey.builder() .register(Order.class, fm -> fm.giveMeBuilder(Order.class) .thenApply((order, b) -> { b.set("totalAmount", order.getItems().stream() .mapToInt(Item::getPrice) .sum() ); }) ) .build();
초보자를 위한 가장 일반적인 옵션
다음은 처음에 가장 필요할 것 같은 필수적인 옵션들입니다:
1. defaultNotNull 옵션 - null 값 방지하기
defaultNotNull
옵션은 명시적으로 null 가능으로 표시되지 않은 프로퍼티가 null이 아니도록 보장합니다(Java의 @Nullable
어노테이션이나 Kotlin의 ?
와 같은 표시가 없는 경우). 이 옵션은 테스트에서 null 관련 문제를 피하고 싶을 때 유용합니다.
@Test
void testDefaultNotNullOption() {
// defaultNotNull 옵션이 적용된 FixtureMonkey 인스턴스 생성
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultNotNull(true) // 이 옵션은 @Nullable 어노테이션이 없는 프로퍼티가 null이 아니도록 보장합니다
.build();
// Product 생성 - 어노테이션이 없는 모든 프로퍼티가 null이 아님
Product product = fixtureMonkey.giveMeOne(Product.class);
assertThat(product.getProductName()).isNotNull();
assertThat(product.getPrice()).isNotNull();
assertThat(product.getCategory()).isNotNull();
}
@Test
fun testDefaultNotNullOption() {
// defaultNotNull 옵션이 적용된 FixtureMonkey 인스턴스 생성
val fixtureMonkey = FixtureMonkey.builder()
.plugin(KotlinPlugin())
.defaultNotNull(true) // 이 옵션은 ? 표시가 없는 프로퍼티가 null이 아니도록 보장합니다
.build()
// Product 생성 - 모든 non-nullable 프로퍼티가 null이 아님
val product = fixtureMonkey.giveMeOne<Product>()
assertThat(product.productName).isNotNull()
assertThat(product.price).isNotNull()
assertThat(product.category).isNotNull()
}
2. javaTypeArbitraryGenerator 옵션 - 기본 타입 생성 제어하기
javaTypeArbitraryGenerator
옵션을 사용하면 기본 Java 타입(String, Integer 등)이 생성되는 방식을 사용자 정의할 수 있습니다. 이 옵션은 Fixture Monkey가 Jqwik 속성 기반 테스팅 라이브러리와 통합되는 JqwikPlugin을 통해 적용됩니다.
@Test
void testJavaTypeArbitraryGeneratorOption() {
// 사용자 정의 문자열 생성이 적용된 FixtureMonkey 생성
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.plugin(
new JqwikPlugin() // JqwikPlugin은 Jqwik 라이브러리와의 통합을 제공합니다
.javaTypeArbitraryGenerator(new JavaTypeArbitraryGenerator() {
@Override
public StringArbitrary strings() {
// 알파벳 문자로만 구성된 문자열 생성하도록 사용자 정의
return Arbitraries.strings().alpha().ofLength(10);
}
})
)
.build();
// 생성된 모든 문자열은 길이가 10인 알파벳 문자열
String generatedString = fixtureMonkey.giveMeOne(String.class);
assertThat(generatedString).hasSize(10);
assertThat(generatedString).matches("[a-zA-Z]+");
}
@Test
fun testJavaTypeArbitraryGeneratorOption() {
// 사용자 정의 문자열 생성이 적용된 FixtureMonkey 생성
val fixtureMonkey = FixtureMonkey.builder()
.plugin(
JqwikPlugin() // JqwikPlugin은 Jqwik 라이브러리와의 통합을 제공합니다
.javaTypeArbitraryGenerator(object : JavaTypeArbitraryGenerator {
override fun strings(): StringArbitrary {
// 알파벳 문자로만 구성된 문자열 생성하도록 사용자 정의
return Arbitraries.strings().alpha().ofLength(10)
}
})
)
.build()
// 생성된 모든 문자열은 길이가 10인 알파벳 문자열
val generatedString = fixtureMonkey.giveMeOne<String>()
assertThat(generatedString).hasSize(10)
assertThat(generatedString).matches("[a-zA-Z]+")
}
3. register 옵션 - 타입별 기본 규칙 설정하기
register
옵션을 사용하면 특정 타입에 대한 기본 설정을 구성할 수 있습니다. 이는 여러 테스트에서 클래스에 일관된 요구 사항이 있을 때 유용합니다.
@Test
void testRegisterOption() {
// Product 클래스에 대한 기본값이 설정된 FixtureMonkey 생성
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.register(
Product.class,
fm -> fm.giveMeBuilder(Product.class)
.set("price", Arbitraries.longs().greaterThan(0)) // 양수 가격만 허용
.set("category", "전자제품") // 고정 카테고리
)
.build();
// 모든 Product는 양수 가격과 '전자제품' 카테고리를 가짐
Product product = fixtureMonkey.giveMeOne(Product.class);
assertThat(product.getPrice()).isPositive();
assertThat(product.getCategory()).isEqualTo("전자제품");
}
@Test
fun testRegisterOption() {
// Product 클래스에 대한 기본값이 설정된 FixtureMonkey 생성
val fixtureMonkey = FixtureMonkey.builder()
.plugin(KotlinPlugin())
.register(Product::class.java) { builder ->
builder.giveMeBuilder<Product>()
.set("price", Arbitraries.longs().greaterThan(0)) // 양수 가격만 허용
.set("category", "전자제품") // 고정 카테고리
}
.build()
// 모든 Product는 양수 가격과 '전자제품' 카테고리를 가짐
val product = fixtureMonkey.giveMeOne<Product>()
assertThat(product.price).isPositive()
assertThat(product.category).isEqualTo("전자제품")
}
4. plugin 옵션 - 기능 확장 추가하기
plugin
옵션을 사용하면 Fixture Monkey가 제공하는 다양한 플러그인의 추가 기능을 통합할 수 있습니다. 이 옵션은 특정 프레임워크나 라이브러리로 작업할 때 필수적입니다.
@Test
void testPluginOption() {
// JSON 지원을 위한 Jackson 플러그인이 적용된 FixtureMonkey 생성
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.plugin(new JacksonPlugin()) // Jackson 어노테이션 지원 추가
.build();
// 이제 Jackson 어노테이션이 제대로 지원되는 객체를 생성할 수 있음
JsonProduct product = fixtureMonkey.giveMeOne(JsonProduct.class);
// JsonProduct의 Jackson 어노테이션이 존중됨
// (예: @JsonProperty, @JsonIgnore 등)
}
@Test
fun testPluginOption() {
// JSON 지원을 위한 Jackson 플러그인이 적용된 FixtureMonkey 생성
val fixtureMonkey = FixtureMonkey.builder()
.plugin(KotlinPlugin()) // Kotlin 기능 지원
.plugin(JacksonPlugin()) // Jackson 어노테이션 지원 추가
.build()
// 이제 Jackson 어노테이션이 제대로 지원되는 객체를 생성할 수 있음
val product = fixtureMonkey.giveMeOne<JsonProduct>()
// JsonProduct의 Jackson 어노테이션이 존중됨
// (예: @JsonProperty, @JsonIgnore 등)
}
5. defaultArbitraryContainerInfoGenerator 옵션 - 컨테이너 크기 제어하기
defaultArbitraryContainerInfoGenerator
옵션을 사용하면 리스트, 세트, 맵과 같은 생성된 컨테이너 타입의 크기를 제어할 수 있습니다. 이 옵션은 테스트에서 특정 크기의 컨테이너가 필요할 때 유용합니다.
@Test
void testContainerSizeOption() {
// 고정 컨테이너 크기가 적용된 FixtureMonkey 생성
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultArbitraryContainerInfoGenerator(context -> new ArbitraryContainerInfo(3, 3)) // 모든 컨테이너는 정확히 3개의 요소를 가짐
.build();
// 정확히 3개의 요소를 가진 리스트 생성
List<String> stringList = fixtureMonkey.giveMeOne(new TypeReference<List<String>>() {});
assertThat(stringList).hasSize(3);
// 정확히 3개의 항목을 가진 맵 생성
Map<Integer, String> map = fixtureMonkey.giveMeOne(new TypeReference<Map<Integer, String>>() {});
assertThat(map).hasSize(3);
}
@Test
fun testContainerSizeOption() {
// 고정 컨테이너 크기가 적용된 FixtureMonkey 생성
val fixtureMonkey = FixtureMonkey.builder()
.defaultArbitraryContainerInfoGenerator { context -> ArbitraryContainerInfo(3, 3) } // 모든 컨테이너는 정확히 3개의 요소를 가짐
.build()
// 정확히 3개의 요소를 가진 리스트 생성
val stringList: List<String> = fixtureMonkey.giveMeOne()
assertThat(stringList).hasSize(3)
// 정확히 3개의 항목을 가진 맵 생성
val map: Map<Int, String> = fixtureMonkey.giveMeOne()
assertThat(map).hasSize(3)
}
어떤 상황에 어떤 옵션을 사용해야 할까요?
다음은 어떤 옵션을 사용해야 할지 선택하는 데 도움이 되는 간단한 가이드입니다:
옵션 | 사용 시점 |
---|---|
defaultNotNull(true) | 테스트 객체에 null 값이 없도록 보장하고 싶을 때 (명시적으로 null 가능한 프로퍼티 제외) |
javaTypeArbitraryGenerator | 문자열이나 숫자와 같은 기본 타입이 생성되는 방식을 사용자 정의해야 할 때 |
register(Class, function) | 특정 클래스에 일관된 기본값이나 제약 조건이 필요할 때 |
plugin(Plugin) | 프레임워크(Jackson, Kotlin 등)에 대한 지원과 같은 추가 기능이 필요할 때 |
defaultArbitraryContainerInfoGenerator | 생성된 컨테이너(리스트, 세트, 맵 등)의 크기를 제어해야 할 때 |
옵션의 적용 범위 이해하기
옵션을 사용할 때 알아야 할 중요한 점들이 있습니다:
- 인스턴스 범위
- 옵션은 해당 FixtureMonkey 인스턴스에서만 적용됩니다
- 여러 인스턴스를 만들어 다른 설정을 적용할 수 있습니다
// 테스트용 설정
FixtureMonkey testFixture = FixtureMonkey.builder()
.defaultNotNull(true)
.build();
// 개발용 설정
FixtureMonkey devFixture = FixtureMonkey.builder()
.defaultNotNull(false)
.build();
- 옵션 우선순위
- 구체적인 옵션이 일반적인 옵션보다 우선합니다
- 나중에 설정된 옵션이 이전 옵션을 덮어씁니다
FixtureMonkey fixture = FixtureMonkey.builder()
.defaultNotNull(true) // 모든 필드 non-null
.register(Product.class, fm -> fm.giveMeBuilder(Product.class)
.setNull("description")) // Product의 description만 null 허용
.build();
다음 단계
Fixture Monkey를 최대한 활용하기 위해 다음 학습 경로를 권장합니다:
- 여기서 시작: 개요(현재 페이지) - 옵션이 무엇이며 어떻게 구성되는지 이해하기
- 초보자를 위한 다음 단계: 초보자를 위한 필수 옵션 - 일상적인 테스트에 필요한 가장 일반적인 옵션 배우기
- 개념 이해하기: 옵션 개념 - 옵션이 내부적으로 어떻게 작동하는지 더 깊이 이해하고 주요 용어 학습하기
- 고급 기능: 전문가를 위한 고급 옵션 - 복잡한 테스트 시나리오를 위한 옵션 탐색하기
초보자라면 우선 초보자를 위한 필수 옵션 문서부터 확인하시고, 필요에 따라 숙련자를 위한 고급 옵션 문서로 진행하세요.