본문으로 건너뛰기
버전: v1.2.x

트레이싱

v1.2.0부터 adapter 엔진이 기본(이자 유일한) 객체 생성 엔진이 되었으며, 내장 tracer를 함께 제공합니다. tracer는 fixture 생성 과정에서 어떤 경로에 어떤 값이 설정되었는지, 각 객체가 어떻게 조립되었는지를 그대로 보여주므로, 특정 필드가 왜 그런 값이 되었는지 혹은 테스트가 왜 실패하는지 디버깅할 때 유용합니다.

tracer는 FixtureMonkeyBuilder#tracer(AssemblyTracer)로 빌더에 직접 연결합니다. 기본값은 트레이싱 비활성(AssemblyTracer.noOp(), 오버헤드 없음)입니다.

tracer 활성화​

FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.tracer(AssemblyTracer.console())
.build();

사용 가능한 tracer​

Tracer설명
AssemblyTracer.noOp()트레이싱 없음 (기본값, 오버헤드 없음)
AssemblyTracer.console()tree 형식 trace를 콘솔에 출력
AssemblyTracer.consoleJson()JSON 형식 trace를 콘솔에 출력
AssemblyTracer.timing()timing 분석만 콘솔에 출력
AssemblyTracer.file(Path path)trace를 파일에 기록 (append 모드)
AssemblyTracer.summary()전체 trace 수집 후 요약 테이블 / timing 분석 출력

trace 읽기​

AssemblyTracer.console()은 디버깅 사고 흐름에 맞춰 섹션을 배치해 출력합니다: 어떤 모드로 빌드했지 → 내가 뭘 설정했지 → 분석 결과는 → 충돌/오버라이드 → register 빌더 → 최종 merge 상태 → 해결 → 최종 결과. 내용이 있는 섹션만 출력됩니다.

섹션내용
Builder Context빌더 상태: isFixed(fixed/결정적 생성 모드), validOnly(strict mode)
Analysis적용된 directive 목록(set / setLazy / container size 등). seq(적용 순서, 나중이 우선), 값, source(DIRECT, REGISTER, REGISTERED_BUILDER, LAZY_EVALUATED). decompose set은 추출/매칭 필드 요약이 추가로 표시됨
Values by Pathadapt 단계에서 분석된 원본 경로→값 매핑 — 사용자가 의도한 설정
Node Collisions나중 set()이 이전 값을 덮어쓴 경로(이전 vs 새 seq/값)
Manipulator Overrides같은 경로에 여러 directive가 충돌할 때 무엇이 우선했고 무엇이 덮어써졌는지
Registered Builders Applied적용된 registered builder(대상 타입, directive 수, container size 수)
Merged Candidatesassemble에 투입되는 최종 USER_SET + REGISTER 값. Values by Path와 비교해 register 병합 확인
Interface Resolutions인터페이스/추상 타입이 구체 타입으로 해결된 정보(declaredType → resolvedType, 사유: PATH_BASED / DEFAULT)
Container Size Resolutions컨테이너 크기 결정 정보(source: EXACT_PATH / TYPE_BASED / WILDCARD / DEFAULT)
Just PathsValues.just()로 설정되어 하위 경로가 무시된 경로
Unresolved Pathsstrict mode에서 매칭되지 않은 경로 — 실패 사유와 사용 가능한 필드 목록
Assembly각 노드가 어떻게 생성되었는지(source, 값, introspector, 생성 방식, 선언/실제 타입)
Timing Informationphase별 소요 시간과 node/manipulator/value 개수
Cache Status캐시 레이어별 히트/미스(baseResult, candidateTree, nodeContext, tree)
Subtree Cache컨테이너 요소로 확장되는 POJO 서브트리에 대한 PromotedSubtreeCache 이벤트(STORE / HIT / MISS / SKIP)
Property Discovery타입별 발견된 필드와 루트 레벨에서 매칭 실패한 타겟

예시: console() 출력​

=================================================================
Fixture Monkey Resolution Trace
=================================================================

▸ Builder Context
isFixed: false
validOnly: false

▸ Analysis (5 manipulators)
├─ OrderSheet.tags ContainerInfo [seq=0] = size=2-2 [REGISTER]
├─ OrderSheet.$ SetLazy [seq=1] = <lazy> [REGISTER]
├─ $.name SetDecomposedValue [seq=2] = "John" [DIRECT]
├─ $.items ContainerInfo [seq=3] = size=2-2 [DIRECT]
└─ $.items[0].id SetDecomposedValue [seq=4] = 123 [DIRECT]

▸ Values by Path (with sequence order)
$.name [seq=0] = "John"
$.items[0].id [seq=2] = 123

▸ Merged Candidates (5 paths, 3 USER_SET, 2 REGISTER)
PATH SOURCE ORDER VALUE
$ USER_SET 2 SimpleObject{name=John, ...}
$.name USER_SET 2 "John"
$.items[0].id USER_SET 4 123
$[type:OrderSheet].tags REGISTER 0 LazyValueHolder@...
$[type:OrderSheet].$ REGISTER 1 LazyValueHolder@...

▸ Interface Resolutions (1)
$.payment Payment → CreditCardPayment [PATH_BASED]

▸ Container Size Resolutions (2)
$.items List<Item> size=2 [EXACT_PATH]
$.tags Set<String> size=2 [REGISTERED_BUILDER]

▸ Assembly (SimpleObject)
└─ $ ← GENERATED (SimpleObject) [via BeanArbitraryIntrospector] (CONSTRUCTOR)
├─ name ← USER_SET = "John" (String)
├─ items ← GENERATED (ArrayList) [via ListIntrospector]
│ └─ [0] ← DECOMPOSED = {id=123, ...} (Item)
│ └─ id ← DECOMPOSED = 123 (int)
└─ status ← GENERATED (String) [null:20%]

▸ Timing Information
Analyze: 0.12 ms
Tree Build: 1.45 ms
Assembly: 0.89 ms
Total: 2.46 ms
=================================================================

timing 분석​

AssemblyTracer.timing()은 각 phase의 소요 시간과 node/manipulator/value 개수를 함께 보고합니다. 큰 객체 그래프 생성을 프로파일링할 때 유용합니다:

=== Adapter Timing ===
Prep: 0.05 ms
Analyze: 0.12 ms
TreeBuild: 1.45 ms
Assembly: 0.89 ms
Total: 2.46 ms
Nodes: 15
CacheHit: false
Manipulators: 3
Values: 2
PathMatches: 5

파일로 출력​

Gradle의 --info 없이 trace를 확인하려면 AssemblyTracer.file(Path)를 사용합니다:

Path tracePath = Paths.get("build/trace-output.txt");
Files.deleteIfExists(tracePath); // 이전 결과 삭제

FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.tracer(AssemblyTracer.file(tracePath))
.build();

fixtureMonkey.giveMeOne(MyObject.class);
// build/trace-output.txt에 tree 형식 trace 저장
  • Append 모드: 여러 trace가 순서대로 축적
  • IO 예외 시 stderr로 메시지 출력 (테스트를 실패시키지 않음)

summary tracer​

AssemblyTracer.summary()는 모든 trace를 수집한 뒤 요약 테이블이나 여러 생성에 대한 timing 분석을 출력합니다:

AssemblyTracer.SummaryTracer tracer = AssemblyTracer.summary();

FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.tracer(tracer)
.build();

fixtureMonkey.giveMeOne(OrderSheet.class);
fixtureMonkey.giveMeOne(Product.class);

tracer.printSummary();
=== Fixture Monkey Summary (2 traces) ===
# | Type | Manipulators | Values | Merged | Unresolved | Time
1 | OrderSheet | 3 | 3 | 5 | 1 | 0.12ms
2 | Product | 1 | 1 | 3 | 0 | 0.05ms
Totals: 2 traces, 4 manipulators, 4 values, 8 merged, 1 unresolved paths, 0.17ms total

성능 분석에는 printTimingBreakdown(long wallClockTimeNanos)으로 wall-clock 시간을 phase별로 나눠 볼 수 있고, getTraces()로 수집된 List<ResolutionTrace>에 프로그래밍 방식으로 접근할 수 있습니다.

디버깅 시나리오별 활용​

질문확인할 섹션
"왜 이 인터페이스가 이 구현체로 되었지?"Interface Resolutions
"왜 리스트 크기가 3개지?"Container Size Resolutions
"왜 내가 설정한 값이 안 먹혔지?"Values by Path, Node Collisions, Merged Candidates
"register 값이 제대로 병합됐나?"Merged Candidates (Values vs Merged 숫자 비교)
"registered builder가 적용됐나?"Registered Builders Applied
"strict mode에서 왜 실패하지?"Unresolved Paths (사유 + 사용 가능 필드)
"Values.just() 때문에 하위 값이 무시됐나?"Just Paths
"fixed() 모드가 적용됐나?"Builder Context
"내 필드가 왜 매칭 안됐지?"Property Discovery, Unresolved Paths
"왜 캐시 히트가 안됐지?"Cache Status, Subtree Cache
팁

tracer에 고정 seed(재현 가능한 생성 참고)를 결합하면, trace를 살펴보는 동안 실패하는 테스트가 항상 동일한 값으로 resolve됩니다.