-
[Fixture Monkey] 불변 객체를 위한 사용자 정의 인트로스펙터 예제 코드 - 기초편📚 개발백과 2025. 10. 6. 12:11728x90
불변 객체(`google.common.collect.Range`)를 위한 사용자 정의 인트로스펙터 예제 코드입니다.
사용 버전: 1.1.11
⬇️ 코드만 먼저 보기
더보기더보기/** * Range<Instant> 타입에 대한 값을 반환하는 사용자 정의 인트로스펙터 예제 */ public class RangeInstantArbitraryIntrospector implements ArbitraryIntrospector { @Override public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) { // 1단계: 이 인트로스펙터가 이 타입을 처리해야 하는지 확인 // Range.class 추출 Property property = context.getResolvedProperty(); Class<?> type = Types.getActualType(property.getType()); // Instant.class 추출 List<AnnotatedType> typeArguments = Types.getGenericsTypes(property.getAnnotatedType()); Class<?> genericType = typeArguments.isEmpty() ? null : Types.getActualType(typeArguments.getFirst()); if (!type.equals(Range.class) || typeArguments.size() != 1 || !genericType.equals(Instant.class)) { // 대상 타입이 아니면 다른 인트로스펙터가 처리하도록 함 return ArbitraryIntrospectorResult.NOT_INTROSPECTED; } // ===== 난수 생성 예시 ===== Instant baseTime = Instant.now(); long offset1 = (long) ((Math.random() * 730) - 365); long offset2 = (long) ((Math.random() * 730) - 365); Instant time1 = baseTime.plus(offset1, ChronoUnit.DAYS); Instant time2 = baseTime.plus(offset2, ChronoUnit.DAYS); Instant startTime = time1.isBefore(time2) ? time1 : time2; Instant endTime = time1.isBefore(time2) ? time2 : time1; // ===== 난수 생성 예시 ===== Range<Instant> rangeValue = Range.closed(startTime, endTime); // 상수 값 반환 return new ArbitraryIntrospectorResult( CombinableArbitrary.from(rangeValue) ); } }// 사용자 정의 인트로스펙터 생성 ArbitraryIntrospector rangeInstantArbitraryIntrospector = new RangeInstantArbitraryIntrospector(); // 사용자 정의 인트로스펙터를 먼저 시도하고, // 적용되지 않으면 표준 인트로스펙터로 대체하는 Fixture Monkey 생성 FixtureMonkey fixtureMonkey = FixtureMonkey.builder() .objectIntrospector(new FailoverIntrospector( Arrays.asList( rangeInstantArbitraryIntrospector, // 사용자 정의 인트로스펙터를 먼저 시도 ConstructorPropertiesArbitraryIntrospector.INSTANCE, BuilderArbitraryIntrospector.INSTANCE, FieldReflectionArbitraryIntrospector.INSTANCE, BeanArbitraryIntrospector.INSTANCE ) )) .build();
Fixture Monkey에 내장되어 있는 인트로스펙터는 다음과 같은 대상 클래스를 지원한다.
- Setter가 있는 클래스
- 생성자가 있는 불변 클래스
- 혼합 필드 접근 방식의 클래스
- 빌더 패턴을 사용하는 클래스
인트로스펙터
인트로스펙터란 무엇인가요? # Fixture Monkey의 인트로스펙터(Introspector)는 테스트 객체를 생성하는 방법을 결정하는 도구입니다. 테스트 객체를 생성하는 “공장"이라고 생각하면 됩니다. 인트로
naver.github.io
하지만 예를 들어
Google Guava 라이브러리의 Range 타입이나
https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html
Java Inet4Address, Inet6Address 타입은
https://docs.oracle.com/javase/8/docs/api/java/net/Inet4Address.html
정적 팩터리 메서드로 생성되는 불변 객체이다.
즉, setter도 없고, public 생성자도 없고, 필드 직접 접근도 불가능하다.
불변 객체도 랜덤 테스트 객체를 지원하기 위해서는 사용자 정의 인트로스펙터를 작성해야 한다.
이 글에서는 Range<Instant> 에 대한 사용자 정의 인트로스펙터를 소개한다.
참고: Fixture Monkey 공식 문서 > 객체 생성 > 사용자 정의 인트로스펙터 만들기
https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/custom-introspector/
import java.time.Instant; import com.google.common.collect.Range; public record Foo( ... Range<Instant> period, ... ) { } // 예시: ["2025-10-06 01:00:00.000+00","2025-10-06 09:00:00.000+00"]Foo 객체 내에 [Instant, Instant] 를 담아 기간을 나타내는 period 필드가 있다.
내장 인트로스펙터만 사용하면 다음과 같은 에러가 발생한다.
// BuilderArbitraryIntrospector가 아니더라도 모든 Intraspector가 아래와 같은 에러를 출력한다. WARN "BuilderArbitraryIntrospector" is failed to introspect "example.demo.foo.model.Foo" type. Failed to generate type "Foo" java.lang.IllegalArgumentException: Failed to generate type "Foo"Foo 객체를 만들지 못하는 이유는 내장 인트로스펙터로는 Range<Instant>를 생성하지 못하기 때문이다.
Range 타입을 지원하지 않는 이유는 해당 타입이 불변 객체이기 때문이다.
Range에 대한 공식 문서를 참고해서 기존 인트로스펙터의 지원 여부를 판단할 수 있다.
https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html
- Setter가 있는 클래스 -> ❎
- 생성자가 있는 불변 클래스 -> ❎
- 혼합 필드 접근 방식의 클래스 -> ❎
- 빌더 패턴을 사용하는 클래스 -> ❎
그렇다면 공식 문서를 참고해 사용자 정의 인트로스펙터를 만들자!
https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/custom-introspector/
/** * Range<Instant> 타입에 대한 값을 반환하는 사용자 정의 인트로스펙터 예제 */ public class RangeInstantArbitraryIntrospector implements ArbitraryIntrospector { @Override public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) { // 1단계: 이 인트로스펙터가 이 타입을 처리해야 하는지 확인 // Range.class 추출 Property property = context.getResolvedProperty(); Class<?> type = Types.getActualType(property.getType()); // Instant.class 추출 List<AnnotatedType> typeArguments = Types.getGenericsTypes(property.getAnnotatedType()); Class<?> genericType = typeArguments.isEmpty() ? null : Types.getActualType(typeArguments.getFirst()); if (!type.equals(Range.class) || typeArguments.size() != 1 || !genericType.equals(Instant.class)) { // 대상 타입이 아니면 다른 인트로스펙터가 처리하도록 함 return ArbitraryIntrospectorResult.NOT_INTROSPECTED; } // ===== 난수 생성 예시 ===== Instant baseTime = Instant.now(); long offset1 = (long) ((Math.random() * 730) - 365); long offset2 = (long) ((Math.random() * 730) - 365); Instant time1 = baseTime.plus(offset1, ChronoUnit.DAYS); Instant time2 = baseTime.plus(offset2, ChronoUnit.DAYS); Instant startTime = time1.isBefore(time2) ? time1 : time2; Instant endTime = time1.isBefore(time2) ? time2 : time1; // ===== 난수 생성 예시 ===== Range<Instant> rangeValue = Range.closed(startTime, endTime); // 상수 값 반환 return new ArbitraryIntrospectorResult( CombinableArbitrary.from(rangeValue) ); } }
인트로스펙터를 커스텀한 후에는
사용할 수 있도록 설정을 해야한다.
// 사용자 정의 인트로스펙터 생성 ArbitraryIntrospector rangeInstantArbitraryIntrospector = new RangeInstantArbitraryIntrospector(); // 사용자 정의 인트로스펙터를 먼저 시도하고, // 적용되지 않으면 표준 인트로스펙터로 대체하는 Fixture Monkey 생성 FixtureMonkey fixtureMonkey = FixtureMonkey.builder() .objectIntrospector(new FailoverIntrospector( Arrays.asList( rangeInstantArbitraryIntrospector, // 사용자 정의 인트로스펙터를 먼저 시도 ConstructorPropertiesArbitraryIntrospector.INSTANCE, BuilderArbitraryIntrospector.INSTANCE, FieldReflectionArbitraryIntrospector.INSTANCE, BeanArbitraryIntrospector.INSTANCE ) )) .build();다른 인트로스펙터 설정 방식은 아래의 아티클에서 기술한다.
(CustomArbitraryIntrospector를 등록하는 여러 방법)
[Fixture Monkey] CustomArbitraryIntrospector를 등록하는 여러 방법
[Fixture Monkey] 불변 객체를 위한 사용자 정의 인트로스펙터 예제 코드 - 기초편불변 객체(`google.common.collect.Range`)를 위한 사용자 정의 인트로스펙터 예제 코드입니다.사용 버전: 1.1.11 ⬇️ 코드만
the0.tistory.com
이후엔 Range<Instant> 타입이 담긴 Foo 객체가 잘 생성되어
테스트 실행 시 에러가 발생하지 않는다.
하지만!
이 예시 클래스인 Range.class 처럼 일부 클래스는
giveMeBuilder().set("period", range) 의 값이 적용되지 않는 문제가 발생할 수도 있다.
이에 관한 내용은 다음 아티클에서 기술한다.
[Fixture Monkey] giveMeBuilder().set()의 값이 적용되지 않음
1. 문제 상황Fixture Monkey 1.1.11 버전 사용 Google Guava 라이브러리의 Range.class는 giveMeBuilder().set()으로 value가 적용되지 않음(String, int, enum 등 자바 기본 자료형은 문제 없음)@Testvoid test_set_range() { Range ta
the0.tistory.com
728x90'📚 개발백과' 카테고리의 다른 글
[Fixture Monkey] CustomArbitraryIntrospector를 등록하는 여러 방법 (0) 2025.10.19 [Spring Data JPA] 서비스 레이어의 어느 메서드에 @Transactional을 안붙이면? (2) 2025.06.27 [Spring Data JPA] 커넥션은 언제 릴리즈 될까 (w/ @Transactional) (0) 2025.06.26 [FastAPI, PostgreSQL] postgresql://와 postgresql+asyncpg:// 의 차이 (1) 2024.10.18 오프라인 상태에서 cURL 사용하기 (0) 2024.10.01