From f30b5f25c79d2c1cd943352d4cc3d10788af5c99 Mon Sep 17 00:00:00 2001 From: Mikonu Date: Tue, 20 Aug 2024 19:22:36 +0900 Subject: [PATCH 1/3] =?UTF-8?q?#97=20-=20Json=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.json` 형식으로 파일을 출력해주는 기능을 추가한다. --- .../domain/constant/ExportFileType.java | 2 +- .../service/exporter/JsonFileExporter.java | 57 +++++++++++ .../exporter/JsonFileExporterTest.java | 95 +++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/main/java/uno/fastcampus/testdata/service/exporter/JsonFileExporter.java create mode 100644 src/test/java/uno/fastcampus/testdata/service/exporter/JsonFileExporterTest.java diff --git a/src/main/java/uno/fastcampus/testdata/domain/constant/ExportFileType.java b/src/main/java/uno/fastcampus/testdata/domain/constant/ExportFileType.java index 0918599..182bc4a 100644 --- a/src/main/java/uno/fastcampus/testdata/domain/constant/ExportFileType.java +++ b/src/main/java/uno/fastcampus/testdata/domain/constant/ExportFileType.java @@ -3,7 +3,7 @@ public enum ExportFileType { CSV, TSV, -// JSON, // TODO: 강의 시간 상 구현을 생략함. 구현하면 TODO 삭제 + JSON, // SQL_INSERT, // TODO: 강의 시간 상 구현을 생략함. 구현하면 TODO 삭제 ; } diff --git a/src/main/java/uno/fastcampus/testdata/service/exporter/JsonFileExporter.java b/src/main/java/uno/fastcampus/testdata/service/exporter/JsonFileExporter.java new file mode 100644 index 0000000..bcef0bf --- /dev/null +++ b/src/main/java/uno/fastcampus/testdata/service/exporter/JsonFileExporter.java @@ -0,0 +1,57 @@ +package uno.fastcampus.testdata.service.exporter; + +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 uno.fastcampus.testdata.domain.constant.ExportFileType; +import uno.fastcampus.testdata.dto.SchemaFieldDto; +import uno.fastcampus.testdata.dto.TableSchemaDto; +import uno.fastcampus.testdata.service.generator.MockDataGeneratorContext; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Slf4j +@RequiredArgsConstructor +@Component +public class JsonFileExporter implements MockDataFileExporter { + + private final MockDataGeneratorContext mockDataGeneratorContext; + private final ObjectMapper mapper; + + @Override + public ExportFileType getType() { + return ExportFileType.JSON; + } + + @Override + public String export(TableSchemaDto dto, Integer rowCount) { + try { + List> list = new ArrayList<>(); + + IntStream.range(0, rowCount).forEach(i -> { + Map map = new LinkedHashMap<>(); + dto.schemaFields().stream() + .sorted(Comparator.comparing(SchemaFieldDto::fieldOrder)) + .forEach(field -> { + map.put(field.fieldName(), mockDataGeneratorContext.generate( + field.mockDataType(), + field.blankPercent(), + field.typeOptionJson(), + field.forceValue() + )); + }); + list.add(map); + }); + + return mapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + log.warn("테이블 스키마 데이터를 JSON으로 변환하는데 실패했습니다 - {}", dto, e); + return ""; + } + } + +} diff --git a/src/test/java/uno/fastcampus/testdata/service/exporter/JsonFileExporterTest.java b/src/test/java/uno/fastcampus/testdata/service/exporter/JsonFileExporterTest.java new file mode 100644 index 0000000..c8e248d --- /dev/null +++ b/src/test/java/uno/fastcampus/testdata/service/exporter/JsonFileExporterTest.java @@ -0,0 +1,95 @@ +package uno.fastcampus.testdata.service.exporter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import uno.fastcampus.testdata.domain.constant.ExportFileType; +import uno.fastcampus.testdata.domain.constant.MockDataType; +import uno.fastcampus.testdata.dto.SchemaFieldDto; +import uno.fastcampus.testdata.dto.TableSchemaDto; +import uno.fastcampus.testdata.service.generator.MockDataGeneratorContext; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.times; + +@DisplayName("[Logic] Json 파일 출력기 테스트") +@ExtendWith(MockitoExtension.class) +class JsonFileExporterTest { + + @InjectMocks private JsonFileExporter sut; + + @Mock private MockDataGeneratorContext mockDataGeneratorContext; + @Spy private ObjectMapper mapper; + + @DisplayName("이 파일 출력기의 유형은 JSON이다.") + @Test + void givenNothing_whenCheckingType_thenReturnsJsonType() { + // Given + + // When & Then + assertThat(sut.getType()).isEqualTo(ExportFileType.JSON); + } + + @DisplayName("테이블 스키마 정보와 행 수가 주어지면, JSON 형식의 문자열을 생성한다.") + @Test + void givenSchemaAndRowCount_whenExporting_thenReturnsJsonFormattedString() throws Exception { + // Given + TableSchemaDto dto = TableSchemaDto.of( + "test_schema", + "uno", + null, + Set.of( + SchemaFieldDto.of("id", MockDataType.ROW_NUMBER, 1, 0, null, null), + SchemaFieldDto.of("name", MockDataType.NAME, 2, 0, null, null), + SchemaFieldDto.of("created_at", MockDataType.DATETIME, 5, 0, null, null), + SchemaFieldDto.of("age", MockDataType.NUMBER, 3, 0, null, null), + SchemaFieldDto.of("car", MockDataType.CAR, 4, 0, null, null) + ) + ); + int rowCount = 10; + given(mockDataGeneratorContext.generate(any(), any(), any(), any())).willReturn("test-value"); + + // When + String result = sut.export(dto, rowCount); + + // Then + System.out.println(result); // 관찰용 + assertThat(result) + .startsWith("[") + .endsWith("]") + .contains(""" + {"id":"test-value","name":"test-value","age":"test-value","car":"test-value","created_at":"test-value"}""" + ); + then(mockDataGeneratorContext).should(times(rowCount * dto.schemaFields().size())).generate(any(), any(), any(), any()); + then(mapper).should().writeValueAsString(any()); + } + + @DisplayName("JSON 변환에 실패하면, 빈 문자열을 반환한다.") + @Test + void givenSchemaAndRowCount_whenFailingToJsonFormatting_thenReturnsEmptyString() throws Exception { + // Given + TableSchemaDto dto = TableSchemaDto.of("test_schema", "uno", null, Set.of(SchemaFieldDto.of("id", MockDataType.ROW_NUMBER, 1, 0, null, null))); + int rowCount = 10; + given(mockDataGeneratorContext.generate(any(), any(), any(), any())).willReturn("test-value"); + willThrow(JsonProcessingException.class).given(mapper).writeValueAsString(any()); + + // When + String result = sut.export(dto, rowCount); + + // Then + assertThat(result).isEqualTo(""); + then(mockDataGeneratorContext).should(times(rowCount * dto.schemaFields().size())).generate(any(), any(), any(), any()); + then(mapper).should().writeValueAsString(any()); + } + +} From 45594f53d6ad7c63a0ffa7e735c4ec483aa02cae Mon Sep 17 00:00:00 2001 From: Mikonu Date: Wed, 21 Aug 2024 01:48:10 +0900 Subject: [PATCH 2/3] =?UTF-8?q?#97=20-=20MockDataType:=20JSON=20=ED=98=B8?= =?UTF-8?q?=ED=99=98=20=ED=83=80=EC=9E=85=20=EB=B0=98=ED=99=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit json 파일 변환기가 각 필드의 자료형마다 알맞은 데이터를 만들어주려면 우선 가짜 데이터 필드 자료형을 json이 이해하는 자료형으로 변환할 필요가 있음. 가짜 데이터 유형 중 기본 유형들은 json에서 서로 구분되는 자료 유형아기 때문에 이 기능을 만들면 활용할 수 있을 것임. --- .../domain/constant/MockDataType.java | 1 + .../domain/constant/MockDataTypeTest.java | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/uno/fastcampus/testdata/domain/constant/MockDataType.java b/src/main/java/uno/fastcampus/testdata/domain/constant/MockDataType.java index f7e1009..c880833 100644 --- a/src/main/java/uno/fastcampus/testdata/domain/constant/MockDataType.java +++ b/src/main/java/uno/fastcampus/testdata/domain/constant/MockDataType.java @@ -39,6 +39,7 @@ public static List toObjects() { } public boolean isBaseType() { return baseType == null; } + public MockDataType jsonType() { return isBaseType() ? this : baseType; } public MockDataTypeObject toObject() { return new MockDataTypeObject( diff --git a/src/test/java/uno/fastcampus/testdata/domain/constant/MockDataTypeTest.java b/src/test/java/uno/fastcampus/testdata/domain/constant/MockDataTypeTest.java index efb8fe3..e329814 100644 --- a/src/test/java/uno/fastcampus/testdata/domain/constant/MockDataTypeTest.java +++ b/src/test/java/uno/fastcampus/testdata/domain/constant/MockDataTypeTest.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import static org.assertj.core.api.Assertions.assertThat; @@ -34,4 +36,35 @@ void givenMockDataType_whenReading_thenReturnsEnumElementObject() { assertThat(result.toString()).contains("name", "requiredOptions", "baseType"); } + @CsvSource(textBlock = """ + STRING, STRING + NUMBER, NUMBER + BOOLEAN, BOOLEAN + DATETIME, DATETIME + ENUM, ENUM + + SENTENCE, STRING + PARAGRAPH, STRING + UUID, STRING + EMAIL, STRING + CAR, STRING + ROW_NUMBER, NUMBER + NAME, STRING + """ + ) + @DisplayName("자료형이 주어지면, JSON에 호환되는 자료형을 반환한다.") + @ParameterizedTest(name = "{index}. {0} => Json Type \"{1}\"") + void givenMockDataType_whenReading_thenReturnsJsonCompatibleMockDataType( + MockDataType input, + MockDataType expected + ) { + // Given + + // When + MockDataType actual = input.jsonType(); + + // Then + assertThat(actual).isEqualTo(expected); + } + } From 96635847b89baafaea8f358427195df98884046f Mon Sep 17 00:00:00 2001 From: Mikonu Date: Wed, 21 Aug 2024 02:24:03 +0900 Subject: [PATCH 3/3] =?UTF-8?q?#97=20-=20Json=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=EA=B8=B0=EA=B0=80=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=9E=90=EB=A3=8C=ED=98=95=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?=EC=95=8C=EB=A7=9E=EC=9D=80=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A5=BC=20=ED=91=9C=ED=98=84=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 대부분의 데이터는 그대로 문자열로 내보내되, 숫자와 boolean 처리를 할 수 있는 분기를 추가하고 `null`값은 변환 에러가 발생할 수 있으므로 그대로 내보내도록 로직 수정 --- .../service/exporter/JsonFileExporter.java | 15 ++++++++++++--- .../service/exporter/JsonFileExporterTest.java | 10 +++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/uno/fastcampus/testdata/service/exporter/JsonFileExporter.java b/src/main/java/uno/fastcampus/testdata/service/exporter/JsonFileExporter.java index bcef0bf..d28d62f 100644 --- a/src/main/java/uno/fastcampus/testdata/service/exporter/JsonFileExporter.java +++ b/src/main/java/uno/fastcampus/testdata/service/exporter/JsonFileExporter.java @@ -11,7 +11,6 @@ import uno.fastcampus.testdata.service.generator.MockDataGeneratorContext; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.IntStream; @Slf4j @@ -37,12 +36,22 @@ public String export(TableSchemaDto dto, Integer rowCount) { dto.schemaFields().stream() .sorted(Comparator.comparing(SchemaFieldDto::fieldOrder)) .forEach(field -> { - map.put(field.fieldName(), mockDataGeneratorContext.generate( + String generatedValue = mockDataGeneratorContext.generate( field.mockDataType(), field.blankPercent(), field.typeOptionJson(), field.forceValue() - )); + ); + if (generatedValue == null) { + map.put(field.fieldName(), null); + } else { + var jsonValue = switch (field.mockDataType().jsonType()) { + case NUMBER -> Long.valueOf(generatedValue); + case BOOLEAN -> Boolean.valueOf(generatedValue); + default -> generatedValue; + }; + map.put(field.fieldName(), jsonValue); + } }); list.add(map); }); diff --git a/src/test/java/uno/fastcampus/testdata/service/exporter/JsonFileExporterTest.java b/src/test/java/uno/fastcampus/testdata/service/exporter/JsonFileExporterTest.java index c8e248d..6b6548d 100644 --- a/src/test/java/uno/fastcampus/testdata/service/exporter/JsonFileExporterTest.java +++ b/src/test/java/uno/fastcampus/testdata/service/exporter/JsonFileExporterTest.java @@ -57,7 +57,11 @@ void givenSchemaAndRowCount_whenExporting_thenReturnsJsonFormattedString() throw ) ); int rowCount = 10; - given(mockDataGeneratorContext.generate(any(), any(), any(), any())).willReturn("test-value"); + given(mockDataGeneratorContext.generate(eq(MockDataType.ROW_NUMBER), any(), any(), any())).willReturn("1"); + given(mockDataGeneratorContext.generate(eq(MockDataType.NAME), any(), any(), any())).willReturn("test-name"); + given(mockDataGeneratorContext.generate(eq(MockDataType.DATETIME), any(), any(), any())).willReturn("2024-01-02T03:04:05"); + given(mockDataGeneratorContext.generate(eq(MockDataType.NUMBER), any(), any(), any())).willReturn(null); + given(mockDataGeneratorContext.generate(eq(MockDataType.CAR), any(), any(), any())).willReturn("test-횬다이"); // When String result = sut.export(dto, rowCount); @@ -68,7 +72,7 @@ void givenSchemaAndRowCount_whenExporting_thenReturnsJsonFormattedString() throw .startsWith("[") .endsWith("]") .contains(""" - {"id":"test-value","name":"test-value","age":"test-value","car":"test-value","created_at":"test-value"}""" + {"id":1,"name":"test-name","age":null,"car":"test-횬다이","created_at":"2024-01-02T03:04:05"}""" ); then(mockDataGeneratorContext).should(times(rowCount * dto.schemaFields().size())).generate(any(), any(), any(), any()); then(mapper).should().writeValueAsString(any()); @@ -78,7 +82,7 @@ void givenSchemaAndRowCount_whenExporting_thenReturnsJsonFormattedString() throw @Test void givenSchemaAndRowCount_whenFailingToJsonFormatting_thenReturnsEmptyString() throws Exception { // Given - TableSchemaDto dto = TableSchemaDto.of("test_schema", "uno", null, Set.of(SchemaFieldDto.of("id", MockDataType.ROW_NUMBER, 1, 0, null, null))); + TableSchemaDto dto = TableSchemaDto.of("test_schema", "uno", null, Set.of(SchemaFieldDto.of("name", MockDataType.NAME, 1, 0, null, null))); int rowCount = 10; given(mockDataGeneratorContext.generate(any(), any(), any(), any())).willReturn("test-value"); willThrow(JsonProcessingException.class).given(mapper).writeValueAsString(any());