본문으로 건너뛰기
버전: 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됩니다.