Skip to content

Add registerByName and selectName with weighted alternatives and @Order - #1352

Open
YongGoose wants to merge 2 commits into
naver:mainfrom
YongGoose:feature/register-by-name
Open

YongGoose wants to merge 2 commits into
naver:mainfrom
YongGoose:feature/register-by-name

Conversation

@YongGoose

@YongGoose YongGoose commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

@seongahjo 오랜만입니다 성아님 ㅎㅎ
오랜 백로그였던.... registerByName 관련해서 PR 올립니다.

방향은 예전 이슈, pr들을 참고해서 진행했습니다.
시간 괜찮으실 때 리뷰 부탁드립니다.

Summary

같은 타입에 이름을 붙여 여러 빌더를 등록하고(registerByName), 샘플마다 그중 하나를 고르거나(#1081) selectName으로 원하는 것을 선택(#1206)하는 기능을 #1343의 Scope 모델 위에 다시 구현합니다.
#1036 · #1062 · #1108 · #1166 · #1205에서 다음 PR로 미뤄 둔 리뷰 코멘트도 함께 반영했습니다.

Closes #1081
Closes #1206

(Optional): Description

API

  • FixtureMonkeyBuilder#registerByName(name, type, builder[, priority[, weight]])
    • 같은 타입에 이름으로 등록한 빌더들은 서로 대안입니다. 선택하지 않으면 샘플마다 하나가 통째로 적용됩니다.
    • priority 숫자가 가장 낮은 것끼리만 경쟁하고, 그 안에서는 가중치 비율로 고릅니다. 기본 가중치는 1이고, 같은 seed는 같은 선택을 합니다.
    • 이름이 중복되면 build()에서, 가중치가 0 이하이면 등록할 때 IllegalArgumentException이 납니다.
  • ExperimentalArbitraryBuilder#selectName(String...)
    • 선택한 이름의 빌더는 샘플 안에서 그 타입이 나오는 모든 곳에 적용됩니다.
    • selectName(a, b)와 selectName(a).selectName(b)는 같습니다. 이미 선택한 이름을 다시 선택하면 가장 나중 선택이 됩니다.
    • 다음 경우에는 예외가 납니다.
      • 등록되지 않은 이름: 샘플링할 때 IAE
      • 객체 전체 값(set("$"), setNull("$"), 샘플된 fixed(), thenApply)을 정한 뒤의 선택: ISE
      • 등록 빌더 안에서 selectName 사용: build()에서 IAE
  • @Order: public으로 바꿨고, registerGroup(Class...)와 registerGroup(ArbitraryBuilderGroup...) 모두에서 그룹의 priority로 씁니다.
  • Kotlin
    • registerByName<T>(name, priority = ..., weight = ...) { ... }
    • giveMeKotlinBuilder<T>().selectName(...)

우선순위 규칙

여러 빌더가 같은 속성에 닿으면 다음 순서로 이깁니다.

  1. 샘플링하는 빌더가 직접 선언한 것(root scope). 호출 순서와 상관없습니다.
  2. selectName으로 선택한 빌더. 나중에 선택한 것이 먼저이고, 타입이 안쪽에 있든 바깥쪽에 있든 상관없습니다.
  3. 선택되지 않은 등록 빌더. Treat register() builders and user directives as scopes #1343 규칙 그대로입니다(바깥 노드 → 낮은 priority → 나중 선언).

값, null, 컨테이너 크기, customizer, instantiate에 모두 같은 순서를 씁니다.
그래서 #1166에서 정식 기능 조건으로 주신 registerNestedSelectFormer와 registerNestedSelectLatter가 둘 다 통과합니다.

"선택된 빌더끼리만 선택 순서를 깊이보다 먼저 본다"는 규칙은 비교에 순환이 생겨서 쓰지 않았습니다.

  • 예: B(안쪽, 나중 선택) > A(바깥, 먼저 선택), A > R(미선택, 가운데), R > B(R이 B보다 바깥).
  • 그래서 선택된 빌더를 등록 빌더 전체보다 한 등급 위에 두었습니다.

구현

  • Scope는 이름, 가중치, 선택 순번(selectionOrder)을 가집니다. 선택된 scope의 priority는 바꾸지 않습니다.
  • NamedScopeResolver(planner)는 샘플마다 같은 selector의 대안 중 선택된 것, 또는 가중치로 고른 하나만 남깁니다.
    • 난수는 SeedState#alternativeSnapshot() 전용 카운터에서 꺼냅니다. 그래서 인터페이스 구현 선택 같은 다른 seed 결정에 영향을 주지 않습니다.
  • DirectivePrecedence는 rank → 깊이 → priority → 선언 순서로 비교합니다.
    • rank는 root가 가장 높고, 선택된 scope는 선택 순번, 그 외는 0입니다.
    • root 판정도 rank로 합니다.
  • ScopeLookup, PathMatcher, ValueProjectionAssembler
    • not-null 양보, "아래에 이기는 지시가 있는지" 판단, customizer 적용이 rank를 먼저 봅니다.
    • 선택되지 않은 scope가 설정한 통째 값 안에, 더 높은 등급으로 선택된 타입이 나올 수 있으면 그 값을 분해합니다. 나올 수 없으면 값을 그대로 씁니다.
  • DefinedScopeContainerSizeResolver, ScopeSet: 컨테이너 크기와 instantiator도 선택된 scope를 먼저 봅니다.
  • MatcherMetadata, Matcher#match(Property, MatcherMetadata), NamedMatcher#metadata는 #1166에서 말씀하신 확장점으로 그대로 둡니다.
  • register(..., priority) Javadoc에 남아 있던 "priority < 0이면 예외" 문구를 지웠습니다. 이 검사는 #1237에서 제거됐습니다.

동작 변화

  • register(MatcherOperator)에 NamedMatcher를 직접 넣으면 이제 이름 등록과 같게 동작합니다.
    • 서로 대안이 되어 샘플마다 하나만 적용됩니다.
    • 같은 이름을 두 번 쓰면 build()에서 예외가 납니다.
    • 이름이 null이면 생성할 때 NPE가 납니다.
  • register(..., Integer.MIN_VALUE)로 등록한 빌더는 예전에 root scope로 판정됐습니다(root 판정이 priority 값 기준이었기 때문입니다). 이제는 일반 등록 빌더로 취급됩니다.

확인 부탁드릴 점

  1. 위 NamedMatcher 동작 변화가 괜찮을까요? EXPERIMENTAL이고 지금은 이름 등록에만 쓰여서 의도한 변화로 두었습니다.
  2. register(..., Integer.MIN_VALUE) 처리 변화가 괜찮을까요?
  3. 빌더가 와일드카드 경로(items[*].tags)로 준 크기가 안쪽 타입에 등록된 빌더의 크기에 지는 동작은 #1343부터 있던 것인데, 의도된 것일까요?
    • 이번 PR에서는 바꾸지 않고, 선택된 빌더도 같은 규칙을 따르게 했습니다.
    • 정확한 경로(items[0].tags)로 준 크기는 빌더가 이깁니다.

Is the Document updated?

네. 두 페이지를 새로 추가했습니다. Docusaurus 빌드는 로컬에서 확인하지 못했습니다.

  • docs/docs/customizing-objects/register-by-name.md
  • docs/i18n/ko/docusaurus-plugin-content-docs/current/customizing-objects/register-by-name.md

다루는 내용은 등록, 선택하지 않았을 때의 우선순위·가중치, selectName, 우선순위 규칙과 예외, 제약 사항, @Order입니다. #1205에서 지운 registerByName.md를 대신합니다.

Builders registered by name for the same type are alternatives: a sample
applies the ones its builder selects with selectName, or one chosen among
the lowest priority number by weight. A selected builder outranks every
registered builder that is not selected, the one selected later first,
wherever their types nest. registerGroup takes the priority of a group
from @order.

Closes naver#1081
Closes naver#1206
… and data classes

Cover a selected name's post condition, map values, nested nodes of a
recursive type, an interface property, a Values.just value, Kotlin data
classes, and that one sample uses the same alternative for every property
of its type. Use the non-deprecated typedString.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant