-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
설계 �구성은 #29 파일 출력기 컨텍스트의 패턴과 동일. 시간 관계상 구현체는 기본이 되는 문자열 생성기 하나만 강의 중에 작성함. 문자열 생성기가 의도에 맞는 문자열을 생성하는지 보는 다양한 테스트들을 작성.
- Loading branch information
Showing
5 changed files
with
379 additions
and
0 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
src/main/java/uno/fastcampus/testdata/service/generator/MockDataGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package uno.fastcampus.testdata.service.generator; | ||
|
||
import uno.fastcampus.testdata.domain.constant.MockDataType; | ||
|
||
/** | ||
* 가짜 데이터를 발생시키는 인터페이스 | ||
* | ||
* @author Uno | ||
*/ | ||
public interface MockDataGenerator { | ||
|
||
/** | ||
* 구현체가 처리하는 가짜 데이터 타입을 반환하는 메소드. | ||
* | ||
* @return 이 구현체가 다루는 가짜 데이터의 유형 | ||
*/ | ||
MockDataType getType(); | ||
|
||
/** | ||
* 가짜 데이터를 생성하는 메소드. | ||
* | ||
* @param blankPercent 빈 값의 비율, 백분율(0 ~ 100). 0이면 빈 값이 없음, 100이면 모두 빈 값({@code null}). | ||
* @param typeOptionJson 가짜 데이터 생성에 필요한 부가 옵션을 담은 JSON 문자열. 구현체에 따라 다르게 해석됨. | ||
* @param forceValue 강제로 설정할 값. 이 값이 주어지면, 가짜 데이터를 생성하지 않고 무조건 이 값으로 반환함. | ||
* @return 생성된 가짜 데이터 문자열 | ||
*/ | ||
String generate(Integer blankPercent, String typeOptionJson, String forceValue); | ||
|
||
} |
32 changes: 32 additions & 0 deletions
32
src/main/java/uno/fastcampus/testdata/service/generator/MockDataGeneratorContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package uno.fastcampus.testdata.service.generator; | ||
|
||
import org.springframework.stereotype.Service; | ||
import uno.fastcampus.testdata.domain.constant.MockDataType; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
public class MockDataGeneratorContext { | ||
|
||
private final Map<MockDataType, MockDataGenerator> mockDataGeneratorMap; | ||
|
||
public MockDataGeneratorContext(List<MockDataGenerator> mockDataGenerators) { | ||
this.mockDataGeneratorMap = mockDataGenerators.stream() | ||
.collect(Collectors.toMap(MockDataGenerator::getType, Function.identity())); | ||
} | ||
|
||
public String generate(MockDataType mockDataType, Integer blankPercent, String typeOptionJson, String forceValue) { | ||
MockDataGenerator generator = mockDataGeneratorMap.get(mockDataType); | ||
|
||
// TODO: 다양한 가짜 데이터 생성기가 도입되면, 이 기본값 강제 설정 코드를 삭제할 것. | ||
if (generator == null) { | ||
generator = mockDataGeneratorMap.get(MockDataType.STRING); | ||
} | ||
|
||
return generator.generate(blankPercent, typeOptionJson, forceValue); | ||
} | ||
|
||
} |
77 changes: 77 additions & 0 deletions
77
src/main/java/uno/fastcampus/testdata/service/generator/StringGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package uno.fastcampus.testdata.service.generator; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import uno.fastcampus.testdata.domain.constant.MockDataType; | ||
import uno.fastcampus.testdata.dto.MockDataDto; | ||
import uno.fastcampus.testdata.repository.MockDataRepository; | ||
|
||
import java.util.List; | ||
import java.util.random.RandomGenerator; | ||
import java.util.stream.Collectors; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
@Component | ||
public class StringGenerator implements MockDataGenerator { | ||
|
||
private final MockDataRepository mockDataRepository; | ||
private final ObjectMapper mapper; | ||
|
||
@Override | ||
public MockDataType getType() { | ||
return MockDataType.STRING; | ||
} | ||
|
||
@Override | ||
public String generate(Integer blankPercent, String typeOptionJson, String forceValue) { | ||
RandomGenerator randomGenerator = RandomGenerator.getDefault(); | ||
if (randomGenerator.nextInt(100) < blankPercent) { | ||
return null; | ||
} | ||
|
||
if (forceValue != null && !forceValue.isBlank()) { | ||
return forceValue; | ||
} | ||
|
||
Option option = new Option(1, 10); // 기본 옵션 | ||
try { | ||
if (typeOptionJson != null && !typeOptionJson.isBlank()) { | ||
option = mapper.readValue(typeOptionJson, Option.class); | ||
} | ||
} catch (JsonProcessingException e) { | ||
log.warn("Json 옵션 정보를 읽어들이는데 실패하였습니다. 기본 옵션으로 동작합니다 - 입력 옵션: {}, 필요 옵션 예: {}", typeOptionJson, option); | ||
} | ||
|
||
if (option.minLength() < 1) { | ||
throw new IllegalArgumentException("[가짜 데이터 생성 옵션 오류] 최소 길이가 1보다 작습니다 - option: " + typeOptionJson); | ||
} else if (option.maxLength() > 10) { | ||
throw new IllegalArgumentException("[가짜 데이터 생성 옵션 오류] 최대 길이가 10보다 큽니다 - option: " + typeOptionJson); | ||
} else if (option.minLength() > option.maxLength()) { | ||
throw new IllegalArgumentException("[가짜 데이터 생성 옵션 오류] 최소 길이가 최대 길이보다 큽니다 - option: " + typeOptionJson); | ||
} | ||
|
||
List<MockDataDto> mockDataDtos = mockDataRepository.findByMockDataType(getType()) | ||
.stream().map(MockDataDto::fromEntity).toList(); | ||
String body = mockDataDtos.stream() | ||
.map(MockDataDto::mockDataValue) | ||
.collect(Collectors.joining("")) | ||
.replaceAll("[^가-힣]", ""); | ||
|
||
|
||
|
||
int difference = option.maxLength() - option.minLength(); | ||
int point = randomGenerator.nextInt(body.length() - option.maxLength); | ||
int offset = (randomGenerator.nextInt(Math.max(1, difference))) + option.minLength(); | ||
|
||
return body.substring(point, point + offset); | ||
} | ||
|
||
public record Option(Integer minLength, Integer maxLength) {} | ||
|
||
} |
30 changes: 30 additions & 0 deletions
30
src/test/java/uno/fastcampus/testdata/service/generator/MockDataGeneratorContextTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package uno.fastcampus.testdata.service.generator; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.test.context.ActiveProfiles; | ||
import uno.fastcampus.testdata.domain.constant.MockDataType; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
@ActiveProfiles("test") | ||
@DisplayName("[IntegrationTest] 파일 출력기 컨텍스트 테스트") | ||
@SpringBootTest | ||
record MockDataGeneratorContextTest(@Autowired MockDataGeneratorContext sut) { | ||
|
||
@DisplayName("문자열 가짜 데이터 타입이 주어지면, 랜덤 문자열을 반환한다.") | ||
@Test | ||
void givenStringType_whenGeneratingMockData_thenReturnsRandomString() { | ||
// Given | ||
|
||
// When | ||
String result = sut.generate(MockDataType.STRING, 0, null, null); | ||
|
||
// Then | ||
System.out.println(result); // 관찰용 | ||
assertThat(result).containsPattern("^[가-힣]{1,10}$"); | ||
} | ||
|
||
} |
211 changes: 211 additions & 0 deletions
211
src/test/java/uno/fastcampus/testdata/service/generator/StringGeneratorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
package uno.fastcampus.testdata.service.generator; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.RepeatedTest; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.CsvSource; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.Spy; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
import uno.fastcampus.testdata.domain.MockData; | ||
import uno.fastcampus.testdata.domain.constant.MockDataType; | ||
import uno.fastcampus.testdata.repository.MockDataRepository; | ||
|
||
import java.util.List; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.catchThrowable; | ||
import static org.mockito.BDDMockito.given; | ||
import static org.mockito.BDDMockito.then; | ||
|
||
@DisplayName("[Logic] 문자열 데이터 생성기 테스트") | ||
@ExtendWith(MockitoExtension.class) | ||
class StringGeneratorTest { | ||
|
||
@InjectMocks private StringGenerator sut; | ||
|
||
@Mock private MockDataRepository mockDataRepository; | ||
@Spy private ObjectMapper mapper; | ||
|
||
@DisplayName("이 테스트 문자열 타입의 가짜 데이터를 다룬다.") | ||
@Test | ||
void givenNothing_whenCheckingType_thenReturnsStringType() { | ||
// Given | ||
|
||
// When & Then | ||
assertThat(sut.getType()).isEqualTo(MockDataType.STRING); | ||
} | ||
|
||
@DisplayName("옵션에 따라 정규식을 통과하는 랜덤 문자열을 생성한다.") | ||
@RepeatedTest(10) | ||
void givenParams_whenGenerating_thenReturnsRandomText() throws Exception { | ||
// Given | ||
MockDataType mockDataType = sut.getType(); | ||
StringGenerator.Option option = new StringGenerator.Option(1, 10); | ||
String optionJson = mapper.writeValueAsString(option); | ||
given(mockDataRepository.findByMockDataType(mockDataType)).willReturn(List.of( | ||
MockData.of(mockDataType, "새침하게 흐린 품이 눈이 올 듯하더니 눈은 아니 오고 얼다가 만 비가 추적추적 내리었다."), | ||
MockData.of(mockDataType, "이날이야말로 동소문 안에서 인력거꾼 노릇을 하는 김 첨지에게는 오래간만에도 닥친 운수 좋은 날이었다."), | ||
MockData.of(mockDataType, "문안에(거기도 문밖은 아니지만) 들어간답시는 앞집 마나님을 전찻길까지 모셔다 드린 것을 비롯으로 행여나 손님이 있을까 하고 정류장에서 어정어정하며 내리는 사람 하나하나에게 거의 비는 듯한 눈결을 보내고 있다가 마침내 교원인 듯한 양복장이를 동광학교(東光學校)까지 태워다 주기로 되었다.") | ||
)); | ||
|
||
// When | ||
String result = sut.generate(0, optionJson, null); | ||
|
||
// Then | ||
System.out.println(result); // 관찰용 | ||
assertThat(result).containsPattern("^[가-힣]{1,10}$"); | ||
then(mapper).should().readValue(optionJson, StringGenerator.Option.class); | ||
then(mockDataRepository).should().findByMockDataType(mockDataType); | ||
} | ||
|
||
@CsvSource(delimiter = '|', textBlock = """ | ||
{"minLength":1,"maxLength":1} | ^[가-힣]{1,1}$ | ||
{"minLength":1,"maxLength":2} | ^[가-힣]{1,2}$ | ||
{"minLength":1,"maxLength":3} | ^[가-힣]{1,3}$ | ||
{"minLength":1,"maxLength":10} | ^[가-힣]{1,10}$ | ||
{"minLength":5,"maxLength":10} | ^[가-힣]{5,10}$ | ||
{"minLength":6,"maxLength":10} | ^[가-힣]{6,10}$ | ||
{"minLength":7,"maxLength":10} | ^[가-힣]{7,10}$ | ||
{"minLength":9,"maxLength":10} | ^[가-힣]{9,10}$ | ||
""") | ||
@DisplayName("옵션에 따라 정규식을 통과하는 랜덤 문자열을 생성한다.") | ||
@ParameterizedTest(name = "{index}. {0} ===> 통과 정규식: {1}") | ||
void givenParams_whenGenerating_thenReturnsRandomText(String optionJson, String expectedRegex) throws Exception { | ||
// Given | ||
MockDataType mockDataType = sut.getType(); | ||
given(mockDataRepository.findByMockDataType(mockDataType)).willReturn(List.of( | ||
MockData.of(mockDataType, "새침하게 흐린 품이 눈이 올 듯하더니 눈은 아니 오고 얼다가 만 비가 추적추적 내리었다."), | ||
MockData.of(mockDataType, "이날이야말로 동소문 안에서 인력거꾼 노릇을 하는 김 첨지에게는 오래간만에도 닥친 운수 좋은 날이었다."), | ||
MockData.of(mockDataType, "문안에(거기도 문밖은 아니지만) 들어간답시는 앞집 마나님을 전찻길까지 모셔다 드린 것을 비롯으로 행여나 손님이 있을까 하고 정류장에서 어정어정하며 내리는 사람 하나하나에게 거의 비는 듯한 눈결을 보내고 있다가 마침내 교원인 듯한 양복장이를 동광학교(東光學校)까지 태워다 주기로 되었다.") | ||
)); | ||
|
||
// When | ||
String result = sut.generate(0, optionJson, null); | ||
|
||
// Then | ||
System.out.println(result); // 관찰용 | ||
assertThat(result).containsPattern("^[가-힣]{1,10}$"); | ||
then(mapper).should().readValue(optionJson, StringGenerator.Option.class); | ||
then(mockDataRepository).should().findByMockDataType(mockDataType); | ||
} | ||
|
||
@DisplayName("잘못된 옵션 내용이 주어지면, 예외를 던진다.") | ||
@CsvSource(delimiter = '|', textBlock = """ | ||
{"minLength":0,"maxLength":0} | ||
{"minLength":0,"maxLength":1} | ||
{"minLength":0,"maxLength":10} | ||
{"minLength":0,"maxLength":11} | ||
{"minLength":1,"maxLength":11} | ||
{"minLength":2,"maxLength":1} | ||
{"minLength":5,"maxLength":1} | ||
{"minLength":10,"maxLength":9} | ||
""") | ||
@ParameterizedTest(name = "{index}. 잘못된 옵션 - {0}") | ||
void givenWrongOption_whenGenerating_thenThrowsException(String optionJson) throws Exception { | ||
// Given | ||
|
||
// When | ||
Throwable t = catchThrowable(() -> sut.generate(0, optionJson, null)); | ||
|
||
// Then | ||
assertThat(t) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessageStartingWith("[가짜 데이터 생성 옵션 오류]"); | ||
then(mapper).should().readValue(optionJson, StringGenerator.Option.class); | ||
then(mockDataRepository).shouldHaveNoInteractions(); | ||
} | ||
|
||
@DisplayName("옵션이 없으면, 기본 옵션으로 정규식을 통과하는 랜덤 문자열을 생성한다.") | ||
@RepeatedTest(10) | ||
void givenNoParams_whenGenerating_thenReturnsRandomText() { | ||
// Given | ||
MockDataType mockDataType = sut.getType(); | ||
given(mockDataRepository.findByMockDataType(mockDataType)).willReturn(List.of( | ||
MockData.of(mockDataType, "새침하게 흐린 품이 눈이 올 듯하더니 눈은 아니 오고 얼다가 만 비가 추적추적 내리었다."), | ||
MockData.of(mockDataType, "이날이야말로 동소문 안에서 인력거꾼 노릇을 하는 김 첨지에게는 오래간만에도 닥친 운수 좋은 날이었다."), | ||
MockData.of(mockDataType, "문안에(거기도 문밖은 아니지만) 들어간답시는 앞집 마나님을 전찻길까지 모셔다 드린 것을 비롯으로 행여나 손님이 있을까 하고 정류장에서 어정어정하며 내리는 사람 하나하나에게 거의 비는 듯한 눈결을 보내고 있다가 마침내 교원인 듯한 양복장이를 동광학교(東光學校)까지 태워다 주기로 되었다.") | ||
)); | ||
|
||
// When | ||
String result = sut.generate(0, null, null); | ||
|
||
// Then | ||
System.out.println(result); // 관찰용 | ||
assertThat(result).containsPattern("^[가-힣]{1,10}$"); | ||
then(mapper).shouldHaveNoInteractions(); | ||
then(mockDataRepository).should().findByMockDataType(mockDataType); | ||
} | ||
|
||
@DisplayName("옵션 json 형식이 잘못되었으면, 기본 옵션으로 정규식을 통과하는 랜덤 문자열을 생성한다.") | ||
@Test | ||
void givenWrongOptionJson_whenGenerating_thenReturnsRandomText() throws Exception { | ||
// Given | ||
MockDataType mockDataType = sut.getType(); | ||
String wrongJson = "{wrong json format}"; | ||
given(mockDataRepository.findByMockDataType(mockDataType)).willReturn(List.of( | ||
MockData.of(mockDataType, "새침하게 흐린 품이 눈이 올 듯하더니 눈은 아니 오고 얼다가 만 비가 추적추적 내리었다."), | ||
MockData.of(mockDataType, "이날이야말로 동소문 안에서 인력거꾼 노릇을 하는 김 첨지에게는 오래간만에도 닥친 운수 좋은 날이었다."), | ||
MockData.of(mockDataType, "문안에(거기도 문밖은 아니지만) 들어간답시는 앞집 마나님을 전찻길까지 모셔다 드린 것을 비롯으로 행여나 손님이 있을까 하고 정류장에서 어정어정하며 내리는 사람 하나하나에게 거의 비는 듯한 눈결을 보내고 있다가 마침내 교원인 듯한 양복장이를 동광학교(東光學校)까지 태워다 주기로 되었다.") | ||
)); | ||
|
||
// When | ||
String result = sut.generate(0, wrongJson, null); | ||
|
||
// Then | ||
System.out.println(result); // 관찰용 | ||
assertThat(result).containsPattern("^[가-힣]{1,10}$"); | ||
then(mapper).should().readValue(wrongJson, StringGenerator.Option.class); | ||
then(mockDataRepository).should().findByMockDataType(mockDataType); | ||
} | ||
|
||
@DisplayName("blank 옵션이 100%면, null을 생성한다.") | ||
@RepeatedTest(10) | ||
void givenNoParams_whenGenerating_thenReturnsNull() { | ||
// Given | ||
|
||
// When | ||
String result = sut.generate(100, null, null); | ||
|
||
// Then | ||
assertThat(result).isEqualTo(null); | ||
then(mapper).shouldHaveNoInteractions(); | ||
then(mockDataRepository).shouldHaveNoInteractions(); | ||
} | ||
|
||
@DisplayName("강제 값이 주어지면, 강제 값을 그대로 반환한다.") | ||
@Test | ||
void givenForcedValue_whenGenerating_thenReturnsForcedValue() { | ||
// Given | ||
String forceValue = "직접입력"; | ||
|
||
// When | ||
String result = sut.generate(0, null, forceValue); | ||
|
||
// Then | ||
assertThat(result).isEqualTo(forceValue); | ||
then(mapper).shouldHaveNoInteractions(); | ||
then(mockDataRepository).shouldHaveNoInteractions(); | ||
} | ||
|
||
@DisplayName("강제 값이 주어지더라도, blank 옵션에 따라 null을 반환할 수 있다.") | ||
@Test | ||
void givenBlankOptionAndForcedValue_whenGenerating_thenReturnsNull() { | ||
// Given | ||
int blankPercent = 100; | ||
String forceValue = "직접입력"; | ||
|
||
// When | ||
String result = sut.generate(blankPercent, null, forceValue); | ||
|
||
// Then | ||
assertThat(result).isNull(); | ||
then(mapper).shouldHaveNoInteractions(); | ||
then(mockDataRepository).shouldHaveNoInteractions(); | ||
} | ||
|
||
} |