ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Fixture Monkey] giveMeBuilder().set()의 값이 적용되지 않음
    🩸 삽질의 추억 2025. 10. 20. 12:31
    728x90

    1. 문제 상황

    Fixture Monkey 1.1.11 버전 사용

     

    Google Guava 라이브러리의 Range.class는 giveMeBuilder().set()으로 value가 적용되지 않음

    (String, int, enum 등 자바 기본 자료형은 문제 없음)

    @Test
    void test_set_range() {
        Range<Instant> targetRange = Range.closed(
                Instant.parse("2025-01-01T00:00:00.000001Z"),
                Instant.parse("2025-12-31T23:59:59.999999Z")
        );
    
        var foo = fixture.giveMeBuilder(Foo.class)
                .setNull("id")
                .set("period", targetRange) // ⭐️ 이 부분
                .sample();
    
        assertThat(foo.getPeriod()).isEqualTo(targetRange);
    }

     

    테스트 결과: 

    org.opentest4j.AssertionFailedError: 
    expected: [2025-01-01T00:00:00.000001Z..2025-12-31T23:59:59.999999Z]
     but was: [2025-05-18T00:59:34.612115Z..2025-11-23T00:59:34.612115Z]

     

    이외 추가 참고 자료... ⬇️

    더보기
    더보기

    Fixture Monkey는 Range 타입 객체 생성을 지원하지 않는다.

    이에 따라 CustomArbitraryIntrospector를 활용해 Range 객체를 생성할 수 있는 RangeInstantArbitraryIntrospector도 만들어 두었다.

    /**
     * Range<Instant> 타입에 대한 값을 반환하는 사용자 정의 인트로스펙터 예제
     */
    public class RangeInstantArbitraryIntrospector implements ArbitraryIntrospector{
        @Override
        public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context){
            Property property = context.getResolvedProperty();
            Class<?> type = Types.getActualType(property.getType());
            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 startTime = // 난수 생성 로직 생략
            Instant endTime = // 난수 생성 로직 생략
    
            Range<Instant> rangeValue = Range.closed(startTime, endTime);
    		
            return new ArbitraryIntrospectorResult(
                    CombinableArbitrary.from(rangeValue)
            );
        }
    }

     

    FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
            .pushArbitraryIntrospector(
                    new MatcherOperator<>(
                            new AssignableTypeMatcher(Range.class).intersect(new SingleGenericTypeMatcher()),
                            rangeInstantArbitraryIntrospector
                    )
            )
            .build();

     

     

     

     

    2. 해결 방법

    Values.just()를 사용한다.

    @Test
    void test_set_range() {
        Range<Instant> targetRange = Range.closed(
                Instant.parse("2025-01-01T00:00:00.000001Z"),
                Instant.parse("2025-12-31T23:59:59.999999Z")
        );
    
        var foo = fixture.giveMeBuilder(Foo.class)
                .setNull("id")
                .set("period", Values.just(targetRange)) // ⭐️ 이 부분
                .sample();
    
        assertThat(foo.getPeriod()).isEqualTo(targetRange);
    }

     

     

    2.1. 추가 설명

    2.1.1 Values.just()

     

    Fixture Monkey는 기본적으로 set()을 사용할 때, 

     

    원본 값을 그대로 사용하지 않고 내부적으로 값을 분해해서 재구성하는 깊은 복사를 수행한다. 

     

    만약 이미 만들어진 객체를 내부 작업(분해-재구성) 없이 직접 할당하고 싶으면 Values.just()를 사용하면 된다.

     

     

    2.1.2. Range 타입 값이 set 되지 않았던 원인

    Fixture Monkey는 Range.class를 깊은 복사하지 못하기 때문이다.

     

    value를 set하기에 앞서, Fixture Monkey는 Range 클래스 자체를 분석한다.

     

    NodeSetDecomposedValueManipulator.java 참고

    isContainer() = false → 일반 객체로 인식

    -> children.isEmpty() = false && isInterface() = false

    -> 루트 객체에 대해서는 setArbitrary() 호출을 하지 않고 자식 필드 값을 처리한다.

    arbitrary 필드란
    이 값이 설정된 경우 사용자가 원하는 값(.set())을,
    설정되지 않으면 ArbitraryIntrospector가 자동 생성하는 값을 사용하도록 지시하는 필드이다.



    .set()을 했음에도 불구하고,

     

    Fixture Monkey는 Range.closed() 분배 후 재조립이 되지 않고,

     

    이는 추후에 arbitrary = null

    -> 커스텀 인트로스펙터인 RangeInstantArbitraryIntrospector 호출

    -> RangeInstantArbitraryIntrospector 내부에서 생성된 랜덤 값이 적용

     

    되는 과정을 거친다.

     

    ※ Values.just() 인 경우, Range.class 클래스 분석 없이 setArbitrary(CombinableArbitrary.from(exactValue)) 를 한다.

     

     

     

    728x90
Designed by Tistory.