diff --git a/tdd/excel-uploader/build.gradle b/tdd/excel-uploader/build.gradle
index 1beb407..f531985 100644
--- a/tdd/excel-uploader/build.gradle
+++ b/tdd/excel-uploader/build.gradle
@@ -22,6 +22,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+ implementation 'org.apache.poi:poi-ooxml:5.2.5'
runtimeOnly 'com.h2database:h2'
diff --git a/tdd/excel-uploader/excel-uploader.drawio b/tdd/excel-uploader/excel-uploader.drawio
new file mode 100644
index 0000000..9f8d62c
--- /dev/null
+++ b/tdd/excel-uploader/excel-uploader.drawio
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tdd/excel-uploader/src/main/java/com/tdd/domain/Application.java b/tdd/excel-uploader/src/main/java/com/tdd/Application.java
similarity index 92%
rename from tdd/excel-uploader/src/main/java/com/tdd/domain/Application.java
rename to tdd/excel-uploader/src/main/java/com/tdd/Application.java
index 4626d10..6084aa7 100644
--- a/tdd/excel-uploader/src/main/java/com/tdd/domain/Application.java
+++ b/tdd/excel-uploader/src/main/java/com/tdd/Application.java
@@ -1,4 +1,4 @@
-package com.tdd.domain;
+package com.tdd;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
diff --git a/tdd/excel-uploader/src/main/java/com/tdd/domain/Driver.java b/tdd/excel-uploader/src/main/java/com/tdd/domain/Driver.java
new file mode 100644
index 0000000..a163a29
--- /dev/null
+++ b/tdd/excel-uploader/src/main/java/com/tdd/domain/Driver.java
@@ -0,0 +1,35 @@
+package com.tdd.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Driver {
+ public Driver() {}
+
+ public Driver(String name, String phoneNumber) {
+ this.name = name;
+ this.phoneNumber = phoneNumber;
+ }
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ private String name = "";
+
+ private String phoneNumber = "";
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getPhoneNumber() {
+ return this.phoneNumber;
+ }
+}
diff --git a/tdd/excel-uploader/src/main/java/com/tdd/domain/DriverRepository.java b/tdd/excel-uploader/src/main/java/com/tdd/domain/DriverRepository.java
new file mode 100644
index 0000000..bd886e6
--- /dev/null
+++ b/tdd/excel-uploader/src/main/java/com/tdd/domain/DriverRepository.java
@@ -0,0 +1,8 @@
+package com.tdd.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface DriverRepository extends JpaRepository {
+}
diff --git a/tdd/excel-uploader/src/main/java/com/tdd/util/excel/ExcelReader.java b/tdd/excel-uploader/src/main/java/com/tdd/util/excel/ExcelReader.java
new file mode 100644
index 0000000..73fa417
--- /dev/null
+++ b/tdd/excel-uploader/src/main/java/com/tdd/util/excel/ExcelReader.java
@@ -0,0 +1,74 @@
+package com.tdd.util.excel;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.web.multipart.MultipartFile;
+
+public class ExcelReader {
+ public static Map> read(MultipartFile file) throws IOException {
+ Map> result = new HashMap<>();
+
+ try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) {
+ Sheet sheet = workbook.getSheetAt(0);
+
+ Row headerRow = sheet.getRow(0);
+
+ String[] headers = new String[headerRow.getLastCellNum()];
+ for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+ Cell cell = headerRow.getCell(i);
+ headers[i] = getCellValue(cell);
+ }
+
+ for (int i = 1; i <= sheet.getLastRowNum(); i++) {
+ Row row = sheet.getRow(i);
+
+ Map rowData = new HashMap<>();
+
+ for (int j = 0; j < headers.length; j++) {
+ Cell cell = row.getCell(j);
+ rowData.put(headers[j], getCellValue(cell));
+ }
+
+ result.put(i, rowData);
+ }
+ }
+
+ return result;
+ }
+
+ private static String getCellValue(Cell cell) {
+ if (cell == null) return "";
+
+ switch (cell.getCellType()) {
+ case STRING:
+ return cell.getStringCellValue();
+ case NUMERIC:
+ if (DateUtil.isCellDateFormatted(cell)) {
+ return cell.getLocalDateTimeCellValue().toString();
+ }
+ double numericValue = cell.getNumericCellValue();
+ if (numericValue == (long) numericValue) {
+ return String.format("%d", (long) numericValue);
+ }
+ return String.valueOf(numericValue);
+ case BOOLEAN:
+ return String.valueOf(cell.getBooleanCellValue());
+ case FORMULA:
+ try {
+ return String.valueOf(cell.getNumericCellValue());
+ } catch (IllegalStateException e) {
+ return cell.getStringCellValue();
+ }
+ default:
+ return "";
+ }
+ }
+}
diff --git a/tdd/excel-uploader/src/main/java/com/tdd/util/excel/ExcelValidator.java b/tdd/excel-uploader/src/main/java/com/tdd/util/excel/ExcelValidator.java
new file mode 100644
index 0000000..3a75960
--- /dev/null
+++ b/tdd/excel-uploader/src/main/java/com/tdd/util/excel/ExcelValidator.java
@@ -0,0 +1,12 @@
+package com.tdd.util.excel;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import com.tdd.util.excel.result.ValidationResult;
+
+public class ExcelValidator {
+
+ public static ValidationResult validate(MultipartFile file) {
+ return new ValidationResult();
+ }
+}
diff --git a/tdd/excel-uploader/src/main/java/com/tdd/util/excel/result/ValidationResult.java b/tdd/excel-uploader/src/main/java/com/tdd/util/excel/result/ValidationResult.java
new file mode 100644
index 0000000..d331f1f
--- /dev/null
+++ b/tdd/excel-uploader/src/main/java/com/tdd/util/excel/result/ValidationResult.java
@@ -0,0 +1,11 @@
+package com.tdd.util.excel.result;
+
+public class ValidationResult {
+ public int getValidResults() {
+ return 0;
+ }
+
+ public int getInvalidResults() {
+ return 0;
+ }
+}
diff --git a/tdd/excel-uploader/src/test/java/com/tdd/domain/DriverRepositoryTest.java b/tdd/excel-uploader/src/test/java/com/tdd/domain/DriverRepositoryTest.java
new file mode 100644
index 0000000..0fa2231
--- /dev/null
+++ b/tdd/excel-uploader/src/test/java/com/tdd/domain/DriverRepositoryTest.java
@@ -0,0 +1,44 @@
+package com.tdd.domain;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+@DataJpaTest
+public class DriverRepositoryTest {
+ private final Driver driver = new Driver("name", "010-1234-1234");
+
+ @Autowired
+ private DriverRepository repository;
+
+ @Test
+ void save_test() {
+ long beforeCount = repository.count();
+
+ repository.save(driver);
+ long afterCount = repository.count();
+
+ assertEquals(beforeCount + 1, afterCount);
+ }
+
+ @Test
+ void save_entity_value_test() {
+ Driver actual = repository.save(driver);
+
+ assertEquals(driver.getName(), actual.getName());
+ assertEquals(driver.getPhoneNumber(), actual.getPhoneNumber());
+ }
+
+ @Test
+ void find_by_id_test() {
+ repository.save(driver);
+
+ Driver actual = repository.findById(driver.getId())
+ .orElseThrow(() -> new IllegalArgumentException("Driver not found"));
+
+ assertEquals(driver.getName(), actual.getName());
+ assertEquals(driver.getPhoneNumber(), actual.getPhoneNumber());
+ }
+}
diff --git a/tdd/excel-uploader/src/test/java/com/tdd/domain/DriverTest.java b/tdd/excel-uploader/src/test/java/com/tdd/domain/DriverTest.java
new file mode 100644
index 0000000..0fd249a
--- /dev/null
+++ b/tdd/excel-uploader/src/test/java/com/tdd/domain/DriverTest.java
@@ -0,0 +1,15 @@
+package com.tdd.domain;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class DriverTest {
+ @Test
+ void create() {
+ Driver actual = new Driver("name", "010-1234-1234");
+
+ assertEquals(actual.getName(), "name");
+ assertEquals(actual.getPhoneNumber(), "010-1234-1234");
+ }
+}
\ No newline at end of file
diff --git a/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelReaderTest.java b/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelReaderTest.java
new file mode 100644
index 0000000..e450b62
--- /dev/null
+++ b/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelReaderTest.java
@@ -0,0 +1,54 @@
+package com.tdd.util.excel;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.web.multipart.MultipartFile;
+
+class ExcelReaderTest {
+ public static final String HEADER_NAME = "기사명";
+ public static final String FIRST_NAME = "홍길동";
+ public static final String SECOND_NAME = "김철수";
+ public static final String HEADER_PHONE_NUMBER = "전화번호";
+ public static final String FIRST_PHONE_NUMBER = "010-1234-5678";
+ public static final String SECOND_PHONE_NUMBER = "010-8765-4321";
+ private MultipartFile VALID_EXCEL;
+ private MultipartFile EMPTY_EXCEL;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ List testData = Arrays.asList(
+ new String[]{FIRST_NAME, FIRST_PHONE_NUMBER},
+ new String[]{SECOND_NAME, SECOND_PHONE_NUMBER}
+ );
+
+ VALID_EXCEL = ExcelTestHelper.createExcelFile(testData);
+ EMPTY_EXCEL = ExcelTestHelper.createEmptyExcelFile();
+ }
+
+ @Test
+ void readExcel() throws IOException {
+ Map> actual = ExcelReader.read(VALID_EXCEL);
+
+ Map firstActual = actual.get(1);
+ assertEquals(FIRST_NAME, firstActual.get(HEADER_NAME));
+ assertEquals(FIRST_PHONE_NUMBER, firstActual.get(HEADER_PHONE_NUMBER));
+
+ Map secondActual = actual.get(2);
+ assertEquals(SECOND_NAME, secondActual.get(HEADER_NAME));
+ assertEquals(SECOND_PHONE_NUMBER, secondActual.get(HEADER_PHONE_NUMBER));
+ }
+
+ @Test
+ void readEmptyExcel() throws IOException {
+ Map> actual = ExcelReader.read(EMPTY_EXCEL);
+
+ assertTrue(actual.isEmpty());
+ }
+}
\ No newline at end of file
diff --git a/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelTestHelper.java b/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelTestHelper.java
new file mode 100644
index 0000000..5a74f59
--- /dev/null
+++ b/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelTestHelper.java
@@ -0,0 +1,70 @@
+package com.tdd.util.excel;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.web.multipart.MultipartFile;
+
+public class ExcelTestHelper {
+ public static MultipartFile createEmptyExcelFile() throws IOException {
+ try (XSSFWorkbook workbook = new XSSFWorkbook()) {
+ XSSFSheet sheet = createSheet(workbook);
+
+ setHeaderRow(sheet);
+
+ byte[] bytes = toByteArray(workbook);
+
+ return convertMockMultipartFile(bytes);
+ }
+ }
+
+ public static MultipartFile createExcelFile(List data) throws IOException {
+ try (XSSFWorkbook workbook = new XSSFWorkbook()) {
+ XSSFSheet sheet = createSheet(workbook);
+
+ setHeaderRow(sheet);
+
+ for (int i = 0; i < data.size(); i++) {
+ XSSFRow dataRow = sheet.createRow(i + 1);
+ String[] rowData = data.get(i);
+ for (int j = 0; j < rowData.length; j++) {
+ dataRow.createCell(j).setCellValue(rowData[j]);
+ }
+ }
+
+ byte[] bytes = toByteArray(workbook);
+
+ return convertMockMultipartFile(bytes);
+ }
+ }
+
+ private static MockMultipartFile convertMockMultipartFile(byte[] bytes) {
+ return new MockMultipartFile(
+ "file",
+ "test.xlsx",
+ "multipart/form-data",
+ bytes
+ );
+ }
+
+ private static byte[] toByteArray(XSSFWorkbook workbook) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ workbook.write(bos);
+ return bos.toByteArray();
+ }
+
+ private static XSSFSheet createSheet(XSSFWorkbook workbook) {
+ return workbook.createSheet("Sheet1");
+ }
+
+ private static void setHeaderRow(XSSFSheet sheet) {
+ XSSFRow headerRow = sheet.createRow(0);
+ headerRow.createCell(0).setCellValue("기사명");
+ headerRow.createCell(1).setCellValue("전화번호");
+ }
+}
diff --git a/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelValidatorTest.java b/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelValidatorTest.java
new file mode 100644
index 0000000..4057e60
--- /dev/null
+++ b/tdd/excel-uploader/src/test/java/com/tdd/util/excel/ExcelValidatorTest.java
@@ -0,0 +1,46 @@
+package com.tdd.util.excel;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.tdd.util.excel.result.ValidationResult;
+
+class ExcelValidatorTest {
+ private MultipartFile EMPTY_EXCEL;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ EMPTY_EXCEL = ExcelTestHelper.createEmptyExcelFile();
+ }
+
+ @Test
+ void validate_has_valid_and_invalid_results() {
+ ValidationResult actual = ExcelValidator.validate(EMPTY_EXCEL);
+
+ assertEquals(0, actual.getValidResults());
+ assertEquals(0, actual.getInvalidResults());
+ }
+
+ @Test
+ @Disabled
+ void validate_for_valid_excel() throws IOException {
+ List testData = Arrays.asList(
+ new String[]{"홍길동", "010-1234-5678"},
+ new String[]{"김철수", "010-8765-4321"}
+ );
+ MultipartFile validTwoRowExcel = ExcelTestHelper.createExcelFile(testData);
+
+ ValidationResult actual = ExcelValidator.validate(validTwoRowExcel);
+
+ assertEquals(2, actual.getValidResults());
+ assertEquals(0, actual.getInvalidResults());
+ }
+}
\ No newline at end of file