@@ -230,6 +230,207 @@ Test Method [MembershipTest#성인_회원만_가능한_테스트] failed with se
230230
231231생성된 값에 대한 더 많은 제어가 필요하거나, ` setPostCondition() ` 이 많은 유효하지 않은 값을 폐기해야 해서 너무 느릴 때는 ` Arbitrary ` 를 사용하세요.
232232
233+ ## 고급 Arbitrary 타입 (실험적 기능)
234+
235+ 버전 1.1.12부터 Fixture Monkey는 값 생성을 더 세밀하게 제어할 수 있는 전용 arbitrary 타입을 제공합니다.
236+
237+ ### CombinableArbitrary.integers()
238+
239+ ` CombinableArbitrary.integers() ` 메서드는 정수 생성을 위한 전용 메서드들을 제공하는 ` IntegerCombinableArbitrary ` 를 반환합니다:
240+
241+ {{< tabpane persist=false >}}
242+ {{< tab header="Java" lang="java">}}
243+ // 다양한 제약조건을 가진 정수 생성
244+ Member member = fixtureMonkey.giveMeBuilder(Member.class)
245+ .set("age", CombinableArbitrary.integers()
246+ .withRange(18, 65) // 18-65세 사이
247+ .positive()) // 양수만
248+ .set("score", CombinableArbitrary.integers()
249+ .even() // 짝수만
250+ .withRange(0, 100)) // 0-100 사이
251+ .sample();
252+ {{< /tab >}}
253+ {{< tab header="Kotlin" lang="kotlin">}}
254+ // 다양한 제약조건을 가진 정수 생성
255+ val member = fixtureMonkey.giveMeBuilder<Member >()
256+ .setExp(Member::age, CombinableArbitrary.integers()
257+ .withRange(18, 65) // 18-65세 사이
258+ .positive()) // 양수만
259+ .setExp(Member::score, CombinableArbitrary.integers()
260+ .even() // 짝수만
261+ .withRange(0, 100)) // 0-100 사이
262+ .sample()
263+ {{< /tab >}}
264+ {{< /tabpane>}}
265+
266+ #### IntegerCombinableArbitrary 메서드
267+
268+ | 메서드 | 설명 | 예시 |
269+ | --------| ------| ------|
270+ | ` withRange(min, max) ` | min과 max 사이의 정수 생성 (양 끝값 포함) | ` integers().withRange(1, 100) ` |
271+ | ` positive() ` | 양수만 생성 (≥ 1) | ` integers().positive() ` |
272+ | ` negative() ` | 음수만 생성 (≤ -1) | ` integers().negative() ` |
273+ | ` even() ` | 짝수만 생성 | ` integers().even() ` |
274+ | ` odd() ` | 홀수만 생성 | ` integers().odd() ` |
275+
276+ ** 중요 참고사항:** 여러 제약조건 메서드를 연결할 때 ** 마지막 메서드가 우선** 됩니다. 예를 들어:
277+ ``` java
278+ // positive() 호출을 무시하고 음수를 생성합니다
279+ CombinableArbitrary . integers(). positive(). negative()
280+
281+ // positive() 호출을 무시하고 10-50 범위의 정수를 생성합니다
282+ CombinableArbitrary . integers(). positive(). withRange(10 , 50 )
283+ ```
284+
285+ ### CombinableArbitrary.strings()
286+
287+ ` CombinableArbitrary.strings() ` 메서드는 문자열 생성을 위한 전용 메서드들을 제공하는 ` StringCombinableArbitrary ` 를 반환합니다:
288+
289+ {{< tabpane persist=false >}}
290+ {{< tab header="Java" lang="java">}}
291+ // 다양한 문자 집합과 제약조건을 가진 문자열 생성
292+ User user = fixtureMonkey.giveMeBuilder(User.class)
293+ .set("username", CombinableArbitrary.strings()
294+ .alphabetic() // 알파벳 문자만
295+ .withLength(5, 15)) // 길이 5-15
296+ .set("password", CombinableArbitrary.strings()
297+ .ascii() // ASCII 문자
298+ .withMinLength(8)) // 최소 8자
299+ .set("phoneNumber", CombinableArbitrary.strings()
300+ .numeric() // 숫자 문자만
301+ .withLength(10, 11)) // 10자리 또는 11자리
302+ .sample();
303+ {{< /tab >}}
304+ {{< tab header="Kotlin" lang="kotlin">}}
305+ // 다양한 문자 집합과 제약조건을 가진 문자열 생성
306+ val user = fixtureMonkey.giveMeBuilder<User >()
307+ .setExp(User::username, CombinableArbitrary.strings()
308+ .alphabetic() // 알파벳 문자만
309+ .withLength(5, 15)) // 길이 5-15
310+ .setExp(User::password, CombinableArbitrary.strings()
311+ .ascii() // ASCII 문자
312+ .withMinLength(8)) // 최소 8자
313+ .setExp(User::phoneNumber, CombinableArbitrary.strings()
314+ .numeric() // 숫자 문자만
315+ .withLength(10, 11)) // 10자리 또는 11자리
316+ .sample()
317+ {{< /tab >}}
318+ {{< /tabpane>}}
319+
320+ #### StringCombinableArbitrary 메서드
321+
322+ | 메서드 | 설명 | 예시 |
323+ | --------| ------| ------|
324+ | ` withLength(min, max) ` | min과 max 사이 길이의 문자열 생성 | ` strings().withLength(5, 10) ` |
325+ | ` withMinLength(min) ` | 최소 길이를 가진 문자열 생성 | ` strings().withMinLength(3) ` |
326+ | ` withMaxLength(max) ` | 최대 길이를 가진 문자열 생성 | ` strings().withMaxLength(20) ` |
327+ | ` alphabetic() ` | 알파벳 문자만 포함하는 문자열 생성 (a-z, A-Z) | ` strings().alphabetic() ` |
328+ | ` ascii() ` | ASCII 문자만 포함하는 문자열 생성 | ` strings().ascii() ` |
329+ | ` numeric() ` | 숫자 문자만 포함하는 문자열 생성 (0-9) | ` strings().numeric() ` |
330+ | ` korean() ` | 한글 문자만 포함하는 문자열 생성 (가-힣) | ` strings().korean() ` |
331+ | ` filterCharacter(predicate) ` | 문자열의 개별 문자를 필터링 | ` strings().filterCharacter(c -> c != 'x') ` |
332+
333+ ** 중요 참고사항:**
334+ 1 . ** 문자 집합 메서드들은 서로 충돌합니다.** 여러 문자 집합 메서드를 연결할 때 ** 마지막 메서드가 우선** 됩니다:
335+ ``` java
336+ // alphabetic()을 무시하고 한글 문자만 생성합니다
337+ CombinableArbitrary . strings(). alphabetic(). korean()
338+ ```
339+
340+ 2 . ** 문자 집합 메서드는 다른 설정 메서드를 무시합니다.** 문자 집합 메서드가 호출되면 이전 설정을 무시하는 새 인스턴스가 생성됩니다:
341+ ``` java
342+ // alphabetic()이 호출되면 withLength(5, 10)이 무시됩니다
343+ CombinableArbitrary . strings(). withLength(5 , 10 ). alphabetic()
344+ ```
345+
346+ ### 고급 필터링
347+
348+ ` IntegerCombinableArbitrary ` 와 ` StringCombinableArbitrary ` 모두 고급 필터링을 지원합니다:
349+
350+ {{< tabpane persist=false >}}
351+ {{< tab header="Java" lang="java">}}
352+ // 사용자 정의 조건으로 정수 필터링
353+ Integer score = CombinableArbitrary.integers()
354+ .withRange(0, 100)
355+ .filter(n -> n % 5 == 0) // 5의 배수만
356+ .combined();
357+
358+ // 사용자 정의 문자 조건으로 문자열 필터링
359+ String code = CombinableArbitrary.strings()
360+ .withLength(6, 8)
361+ .filterCharacter(c -> Character.isUpperCase(c) || Character.isDigit(c)) // 대문자와 숫자만
362+ .combined();
363+ {{< /tab >}}
364+ {{< tab header="Kotlin" lang="kotlin">}}
365+ // 사용자 정의 조건으로 정수 필터링
366+ val score = CombinableArbitrary.integers()
367+ .withRange(0, 100)
368+ .filter { it % 5 == 0 } // 5의 배수만
369+ .combined()
370+
371+ // 사용자 정의 문자 조건으로 문자열 필터링
372+ val code = CombinableArbitrary.strings()
373+ .withLength(6, 8)
374+ .filterCharacter { it.isUpperCase() || it.isDigit() } // 대문자와 숫자만
375+ .combined()
376+ {{< /tab >}}
377+ {{< /tabpane>}}
378+
379+ ### 실제 사례: 사용자 등록 검증
380+
381+ {{< tabpane persist=false >}}
382+ {{< tab header="Java" lang="java">}}
383+ @Test
384+ void 다양한_입력으로_사용자_등록_검증() {
385+ for (int i = 0; i < 100; i++) {
386+ User user = fixtureMonkey.giveMeBuilder(User.class)
387+ .set("username", CombinableArbitrary.strings()
388+ .alphabetic()
389+ .withLength(3, 20)) // 유효한 사용자명: 3-20자 알파벳
390+ .set("email", CombinableArbitrary.strings()
391+ .ascii()
392+ .withLength(5, 50)
393+ .filter(s -> s.contains("@"))) // 간단한 이메일 검증
394+ .set("age", CombinableArbitrary.integers()
395+ .withRange(13, 120)) // 유효한 나이 범위
396+ .set("score", CombinableArbitrary.integers()
397+ .withRange(0, 100)
398+ .filter(n -> n % 10 == 0)) // 10의 배수인 점수
399+ .sample();
400+
401+ // 다양한 유효한 입력으로 테스트
402+ ValidationResult result = userService.validateRegistration(user);
403+ assertThat(result.isValid()).isTrue();
404+ }
405+ }
406+ {{< /tab >}}
407+ {{< tab header="Kotlin" lang="kotlin">}}
408+ @Test
409+ fun 다양한_입력으로_사용자_등록_검증() {
410+ repeat(100) {
411+ val user = fixtureMonkey.giveMeBuilder<User >()
412+ .setExp(User::username, CombinableArbitrary.strings()
413+ .alphabetic()
414+ .withLength(3, 20)) // 유효한 사용자명: 3-20자 알파벳
415+ .setExp(User::email, CombinableArbitrary.strings()
416+ .ascii()
417+ .withLength(5, 50)
418+ .filter { it.contains("@") }) // 간단한 이메일 검증
419+ .setExp(User::age, CombinableArbitrary.integers()
420+ .withRange(13, 120)) // 유효한 나이 범위
421+ .setExp(User::score, CombinableArbitrary.integers()
422+ .withRange(0, 100)
423+ .filter { it % 10 == 0 }) // 10의 배수인 점수
424+ .sample()
425+
426+ // 다양한 유효한 입력으로 테스트
427+ val result = userService.validateRegistration(user)
428+ assertThat(result.isValid).isTrue()
429+ }
430+ }
431+ {{< /tab >}}
432+ {{< /tabpane>}}
433+
233434## 추가 자료
234435
235436모든 Arbitrary 유형과 메서드에 대한 자세한 내용은 [ Jqwik 사용자 가이드] ( https://jqwik.net/docs/current/user-guide.html#static-arbitraries-methods ) 를 참조하세요.
0 commit comments