diff --git a/README.md b/README.md index 762a97926..2e2e7e96c 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,66 @@ # java-attendance ## 기능 목록 +- [X] src/main/resources/attendances.csv 파일을 이용하여 필요한 정보를 가져와 저장한다. - [X] 1번을 눌렀을 경우 - [X] 닉네임을 입력받는다. - [X] 등교 시간을 입력받는다. (등교시간의 경우 오늘 등교를 하는 것이나 12월 이라는 조건으로 오늘을 2024.12.13 으로 설정하였습니다) - - [X] 정보를 업데이트 하고, 출력한다. + - [X] 요일별 등교 시간을 확인한다. + - [X] 요일별 등교 시간을 기반으로 입력 받은 등교 시간과 비교하여 출석, 지각, 결석을 판단한다. + - [X] 출결 정보를 등록 + - [X] 등록한 정보 출력한다. - [X] 2번을 눌렀을 경우 - [X] 닉네임을 입력받는다. - [X] 날짜를 입력받는다. - [X] 변경할 시간을 입력받는다. - - [X] 정보를 업데이트 하고, 출력한다. + - [X] 변경한 시간을 기반으로 출석, 지각, 결석을 판단한다. + - [X] 정보를 수정한다. + - [X] 바뀐 정보를 출력한다. - [X] 3번을 눌렀을 경우 - [X] 닉네임을 입력받는다. - - [X] 출석 기록을 출력한다. + - [X] 오늘을 기준으로 그 전 날짜들의 출석 기록이 없는 경우 결석으로 처리한다. + - [X] 입력 받은 크루의 출석 기록을 출력한다. - [X] 4번을 눌렀을 경우 - - [X] 제적 위험자를 출력한다. + - [X] 크루들의 출석 기록들을 확인한다. + - [X] 크루들의 출결 기록 횟수들을 계산한다. + - [X] 지각 3회는 결석 1회로 간주하여 계산한다. + - [X] 제적 위험자 조건에 맞는 크루들을 찾는다. + - [X] 출력한다. - [X] Q를 눌렀을 경우 - [X] 프로그램을 종료한다. --- +## 예외 처리 +- [X] 존재하지 않는 닉네임을 입력한 경우 +- [X] 존재하지 않는 기록을 수정하려고 할 경우 +- [X] 메뉴 선택이 잘못된 경우 +- [X] 등교 시간을 잘못 적은 + - [X] 잘못된 형식으로 입력한 경우 + - [X] 캠퍼스 운영 시간이 아닌 경우 + - [X] 주말 및 공휴일에 출석을 한 경우 + - [X] 이미 출석한 경우 +--- ## 요구 사항 - 2024년 12월 한 달 동안 시범적으로 최소한의 기능을 갖춘 출석 시스템입니다. - 교육 시작 시간으로부터 5분 초과 시 지각 - 교육 시작 시간으로부터 30분 초과 시 결석 - 지각 3회는 결석 1회로 간주한다. -- 결석 5회 초과는 제적 대상자. ---- -## 예외 처리 -- [X] 존재하지 않는 닉네임을 입력한 경우 - -- [X] 등교 시간을 잘못 적은 경우 - - [X] 잘못된 형식으로 입력한 경우 - - [X] 캠퍼스 운영 시간이 아닌 경우 - - [X] 주말 및 공휴일에 출석을 한 경우 +- 경고 대상자: 결석 2회 이상 +- 면담 대상자: 결석 3회 이상 +- 제적 대상자: 결석 5회 초과 +- 구현 요구사항을 만족하는 최소한의 설계를 한다. --- +## 프로그래밍 요구 사항 +- 자바 코드 컨벤션을 지키면서 프로그래밍한다. (기본적으로 Java Style Guide을 원칙으로 한다.) +- indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다. +- 3항 연산자를 쓰지 않는다. +- else 예약어를 쓰지 않는다. (else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.) +- 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외 +- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다. +- UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다. +- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. +- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. +- 배열 대신 컬렉션을 사용한다. +- Java Enum을 적용한다. +- 모든 원시 값과 문자열을 포장한다 +- 줄여 쓰지 않는다(축약 금지). +- 일급 컬렉션을 쓴다. diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 000000000..3ef99c0fc --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,9 @@ +import controller.Controller; +import java.io.IOException; + +public class Main { + public static void main(String[] args) throws IOException { + Controller controller = new Controller(); + controller.run(); + } +} diff --git a/src/main/java/constant/DateFormatInformation.java b/src/main/java/constant/DateFormatInformation.java deleted file mode 100644 index 11535f537..000000000 --- a/src/main/java/constant/DateFormatInformation.java +++ /dev/null @@ -1,6 +0,0 @@ -package constant; - -public class DateFormatInformation { - public static final String LOCAL_DATE_TIME_FORMATTER = "yyyy-MM-dd HH:mm"; - public static final String LOCAL_TIME_FORMATTER = "HH:mm"; -} diff --git a/src/main/java/constant/MenuOption.java b/src/main/java/constant/MenuOption.java new file mode 100644 index 000000000..e613720da --- /dev/null +++ b/src/main/java/constant/MenuOption.java @@ -0,0 +1,28 @@ +package constant; + +import java.util.Arrays; + +public enum MenuOption { + ATTENDANCE_REGISTER("1"), + ATTENDANCE_MODIFY("2"), + CREW_ATTENDANCE_CHECK("3"), + EXPULSION_RISK("4"), + QUIT("Q"); + + private final String inputMenuValue; + + MenuOption(String inputMenuValue) { + this.inputMenuValue = inputMenuValue; + } + + public static MenuOption validateSelectMenuOption(String input) { + return Arrays.stream(MenuOption.values()) + .filter(option -> option.getInputMenuValue().equals(input)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("[ERROR] 유효하지 않은 선택입니다." + input)); + } + + public String getInputMenuValue() { + return inputMenuValue; + } +} diff --git a/src/main/java/controller/Controller.java b/src/main/java/controller/Controller.java index a6cbfe3c4..7808beff9 100644 --- a/src/main/java/controller/Controller.java +++ b/src/main/java/controller/Controller.java @@ -1,163 +1,134 @@ package controller; -import constant.DateFormatInformation; +import constant.MenuOption; import java.io.IOException; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; +import java.time.LocalTime; import java.util.List; -import model.Student; import model.AttendanceBook; -import util.FileInput; -import util.LocalDateTimePrintFormatter; +import model.Student; +import util.AttendanceRecordFormatter; +import util.FileInformationProvider; import view.InputView; -import view.OutputView; +import view.OutPutView; public class Controller { - private static final LocalDateTime TODAY = LocalDateTime.of(2024, 12, 13, 10, 0); - private static final int CHECK_IN = 1; - private static final int MODIFY_ATTENDANCE = 2; - private static final int CHECK_BY_CREW = 3; - private static final int CHECK_DROPOUT_RISK = 4; - - - private AttendanceBook readFileAndCreateStudentRepository() throws IOException { - FileInput fileInput = new FileInput(); - List students = fileInput.createStudents(); - return new AttendanceBook(students); - } - - public AttendanceBook createStudentRepository() { - try { - return readFileAndCreateStudentRepository(); - } catch (IOException e) { - return createStudentRepository(); - } - } - - public void attendanceStart() { - LocalDate todayDate = LocalDate.from(TODAY); - InputView.printTodayAndSelectFunction(todayDate); - AttendanceBook studentRepository = createStudentRepository(); - - for (Student student : studentRepository.getStudents()) { - student.getAttendanceRecords().updateStateNotExistInFile(todayDate); - } + private static final LocalDate TODAY = LocalDate.of(2024, 12, 13); + public void run() throws IOException { + AttendanceBook attendanceBook = new AttendanceBook(FileInformationProvider.loadStudentAttendance()); + attendanceBook.updateNonExistentAttendanceRecords(TODAY); while (true) { - String selectFunction = InputView.getUserInputString(); - if (selectFunction.equals("Q")) { - break; - } - if (Integer.parseInt(selectFunction) == CHECK_IN) { - functionForMenuOne(studentRepository, todayDate); - } - if (Integer.parseInt(selectFunction) == MODIFY_ATTENDANCE) { - functionForMenuTwo(studentRepository); - } - if (Integer.parseInt(selectFunction) == CHECK_BY_CREW) { - functionForMenuThree(studentRepository); - } - if (Integer.parseInt(selectFunction) == CHECK_DROPOUT_RISK) { - functionForMenuFour(studentRepository); + if (handleMenuChoice(attendanceBook)) { + return; } } - - } - - private static void functionForMenuFour(AttendanceBook studentRepository) { - OutputView.printEveryStudentPunishmentLabel(studentRepository); - } - - private void functionForMenuThree(AttendanceBook studentRepository) { - String studentName = getStudentForAttendanceCheckUntilExist(studentRepository); - OutputView.printAttendanceRecord(studentRepository - .findStudentByName(studentName).getAttendanceRecords().getRecord()); - studentRepository.findStudentByName(studentName).calculateAbsent(); - OutputView.printStudentState(studentRepository.findStudentByName(studentName)); - OutputView.printStudentPunishmentLabel(studentRepository.findStudentByName(studentName)); - } - - private void functionForMenuTwo(AttendanceBook studentRepository) { - String studentName = getStudentNameForModifyUntilValidate(studentRepository); - Student student = studentRepository.findStudentByName(studentName); - LocalDateTime modifyLocalDateTime = getLocalDateTimeToModify(); - LocalDate modifyLocalDate = LocalDate.from(modifyLocalDateTime); - String recordBeforeModify = LocalDateTimePrintFormatter - .LocalDateTimeToLocalTime(modifyLocalDate, - student.getAttendanceRecords().getRecord().get(modifyLocalDate)) + - student.findStateByLocalDateTime(modifyLocalDateTime); - - student.modifyAttendanceRecord(modifyLocalDateTime); - String recordAfterModify = student.findStateByLocalDateTime(modifyLocalDateTime); - - String localDateTimeFormat = modifyLocalDateTime.format(DateTimeFormatter.ofPattern( - DateFormatInformation.LOCAL_TIME_FORMATTER + " (" + recordAfterModify + ") 수정 완료!")); - - OutputView.printSecondMenu(recordBeforeModify, localDateTimeFormat); } - private LocalDateTime getLocalDateTimeToModify() { - int modifyDate = InputView.inputDateForModify(); - InputView.printTimeForModify(); - LocalDate localDate = LocalDate.of(2024, 12, modifyDate); - return getTimeUntilValidate(localDate); - } - - private void functionForMenuOne(AttendanceBook studentRepository, LocalDate todayDate) { - String studentName = getStudentForAttendanceCheckUntilExist(studentRepository); - LocalDateTime localDateTime = getLocalDateTimeUntilValidate(todayDate); - Student student = studentRepository.findStudentByName(studentName); - student.attendanceRegister(localDateTime); - OutputView.printTodayAttendanceResult(student, localDateTime); + private boolean handleMenuChoice(AttendanceBook attendanceBook) { + OutPutView.displayAttendanceMenu(TODAY); + MenuOption menuOption = chooseMenuOption(); + if (menuOption.equals(MenuOption.ATTENDANCE_REGISTER)) { + registerAttendance(attendanceBook); + } + if (menuOption.equals(MenuOption.ATTENDANCE_MODIFY)) { + modifyAttendance(attendanceBook); + } + if (menuOption.equals(MenuOption.CREW_ATTENDANCE_CHECK)) { + checkCrewAttendance(attendanceBook); + } + if (menuOption.equals(MenuOption.EXPULSION_RISK)) { + checkExpulsionRisk(attendanceBook); + } + return menuOption.equals(MenuOption.QUIT); } - private String getStudentNameForModifyUntilValidate(AttendanceBook studentRepository) { + private MenuOption chooseMenuOption() { try { - InputView.printStudentNameForModify(); - return getStudentNameUntilExist(studentRepository); + return InputView.inputChooseFunctionOption(); } catch (IllegalArgumentException e) { - return getStudentNameForModifyUntilValidate(studentRepository); + System.out.println(e.getMessage()); + return chooseMenuOption(); } } - private LocalDateTime getLocalDateTimeUntilValidate(LocalDate todayDate) { + private void registerAttendance(AttendanceBook attendanceBook) { try { - InputView.printStartTime(); - return getTimeUntilValidate(todayDate); + String nickName = requestNickName(); + LocalTime attendanceTime = requestAttendanceTime(); + Student student = attendanceBook.findStudentByNickName(nickName); + student.registerAttendanceRecord(TODAY, attendanceTime); + OutPutView.displayRegisterAttendanceRecord(AttendanceRecordFormatter.attendanceRecordFormatter( + student, TODAY)); } catch (IllegalArgumentException e) { - return getLocalDateTimeUntilValidate(todayDate); + System.out.println(e.getMessage()); + registerAttendance(attendanceBook); } } - private String getStudentForAttendanceCheckUntilExist(AttendanceBook studentRepository) { - InputView.printInputNicName(); - try { - return getStudentNameUntilExist(studentRepository); - } catch (IllegalArgumentException e) { - return getStudentForAttendanceCheckUntilExist(studentRepository); - } + private static LocalTime requestAttendanceTime() { + OutPutView.requestAttendanceTime(); + return InputView.inputAttendanceTime(); } - private String getStudentNameUntilExist(AttendanceBook studentRepository) { - String userName = InputView.userInput(); + private void modifyAttendance(AttendanceBook attendanceBook) { try { - studentRepository.notExistStudent(userName); - return userName; + String nickName = requestModifyNickName(); + int modifyDate = requestModifyDate(); + Student student = attendanceBook.findStudentByNickName(nickName); + modifyAttendanceRecord(student, modifyDate); } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); - throw new IllegalArgumentException(); + modifyAttendance(attendanceBook); } } - private LocalDateTime getTimeUntilValidate(LocalDate localDate) { + private void checkCrewAttendance(AttendanceBook attendanceBook) { try { - LocalDateTime localDateTimeToAttendanceCheck = InputView.makeLocalDateToLocalDateTime(localDate); - InputView.isNotOpeningHour(localDateTimeToAttendanceCheck); - return localDateTimeToAttendanceCheck; + String nickName = requestNickName(); + Student student = attendanceBook.findStudentByNickName(nickName); + OutPutView.displayTotalAttendanceRecord(student); + OutPutView.displayTotalAttendanceCount(student); + OutPutView.displayCounselingCandidate(student); } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); - throw new IllegalArgumentException(); + checkCrewAttendance(attendanceBook); } } + + private void checkExpulsionRisk(AttendanceBook attendanceBook) { + List expulsionRiskStudents = attendanceBook.findExpulsionRiskStudents(); + OutPutView.displayExpulsionRiskStudents(expulsionRiskStudents); + } + + private void modifyAttendanceRecord(Student student, int modifyDate) { + LocalDate date = LocalDate.of(2024, 12, modifyDate); + String beforeRecord = AttendanceRecordFormatter.attendanceRecordFormatter(student, date); + + LocalTime modifyTime = requestModifyTime(); + student.modifyAttendanceRecord(modifyDate, modifyTime); + + String afterRecord = AttendanceRecordFormatter.attendanceRecordFormatter(student, date); + OutPutView.displayModifyAttendanceRecord(beforeRecord, afterRecord); + } + + private static String requestNickName() { + OutPutView.requestNickName(); + return InputView.input(); + } + + private String requestModifyNickName() { + OutPutView.requestModifyNickName(); + return InputView.input(); + } + + private int requestModifyDate() { + OutPutView.requestModifyDate(); + return InputView.validateDateFormat(InputView.input()); + } + + private LocalTime requestModifyTime() { + OutPutView.requestModifyTime(); + return InputView.validateTimeFormat(InputView.input()); + } } diff --git a/src/main/java/main.java b/src/main/java/main.java deleted file mode 100644 index 7d2b077d8..000000000 --- a/src/main/java/main.java +++ /dev/null @@ -1,8 +0,0 @@ -import controller.Controller; - -public class main { - public static void main(String[] args) { - Controller controller = new Controller(); - controller.attendanceStart(); - } -} diff --git a/src/main/java/model/AttendanceBook.java b/src/main/java/model/AttendanceBook.java index 1a5545026..64220d0ed 100644 --- a/src/main/java/model/AttendanceBook.java +++ b/src/main/java/model/AttendanceBook.java @@ -1,28 +1,42 @@ package model; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class AttendanceBook { private final List students; - public AttendanceBook(List students) { - this.students = students; + public AttendanceBook(List attendanceBook) { + this.students = attendanceBook; } - public void notExistStudent(String studentName) { - if (findStudentByName(studentName) == null) { - throw new IllegalArgumentException("[ERROR] 등록되지 않은 닉네임입니다."); + public AttendanceBook(Map> fileAttendanceRecord) { + students = fileAttendanceRecord.entrySet().stream() + .map(entry -> new Student(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + + public void updateNonExistentAttendanceRecords(LocalDate today) { + for (Student student : students) { + student.updateNonAttendanceRecordStatusIsAbsent(today); } } - public Student findStudentByName(String name) { + public Student findStudentByNickName(String name) { return students.stream() - .filter(s -> s.getName().equals(name)) + .filter(stu -> stu.getName().equals(name)) .findFirst() - .orElse(null); + .orElseThrow(() -> new IllegalArgumentException("[ERROR] 찾는 학생이 존재하지 않습니다.")); } - public List getStudents() { - return students; + public List findExpulsionRiskStudents() { + return students.stream() + .filter(Student::isAtRiskOfCounselingOrExpulsion) + .sorted(Comparator.comparing(Student::calculateTotalAbsentCount)) + .collect(Collectors.toList()); } } diff --git a/src/main/java/model/AttendanceCount.java b/src/main/java/model/AttendanceCount.java deleted file mode 100644 index 7967583a6..000000000 --- a/src/main/java/model/AttendanceCount.java +++ /dev/null @@ -1,29 +0,0 @@ -package model; - -import java.util.Map; - -public class AttendanceCount { - private final Map attendanceCount; - - public AttendanceCount(Map attendanceCount) { - this.attendanceCount = attendanceCount; - } - - public void updateAttendanceCount(AttendanceRecords attendanceRecords){ - attendanceCount.put(AttendanceStatus.ATTENDANCE, attendanceRecords.findTotalAttendanceCount()); - attendanceCount.put(AttendanceStatus.LATE, attendanceRecords.findTotalLateCount()); - attendanceCount.put(AttendanceStatus.ABSENT, attendanceRecords.findTotalAbsentCount()); - } - - public long getAttendanceTotalCount(){ - return attendanceCount.get(AttendanceStatus.ATTENDANCE); - } - - public long getLateTotalCount(){ - return attendanceCount.get(AttendanceStatus.LATE); - } - - public long getAbsentTotalCount(){ - return attendanceCount.get(AttendanceStatus.ABSENT); - } -} diff --git a/src/main/java/model/AttendancePenalty.java b/src/main/java/model/AttendancePenalty.java new file mode 100644 index 000000000..d4a07c336 --- /dev/null +++ b/src/main/java/model/AttendancePenalty.java @@ -0,0 +1,35 @@ +package model; + +import java.util.Arrays; +import java.util.Comparator; + +public enum AttendancePenalty { + NONE("없음", 0), + WARNING("경고", 2), + COUNSELING("면담", 3), + EXPULSION("제적", 6); + + private final String penalty; + private final int thresholdAbsenceCount; + + AttendancePenalty(String penalty, int thresholdAbsenceCount) { + this.penalty = penalty; + this.thresholdAbsenceCount = thresholdAbsenceCount; + } + + public static AttendancePenalty findPenaltyByAbsentCount(long totalAbsentCount) { + return Arrays.stream(AttendancePenalty.values()) + .sorted(Comparator.comparingInt(AttendancePenalty::getThresholdAbsenceCount).reversed()) + .filter(penalty -> totalAbsentCount >= penalty.thresholdAbsenceCount) + .findFirst() + .orElse(NONE); + } + + public String getPenalty() { + return penalty; + } + + public int getThresholdAbsenceCount() { + return thresholdAbsenceCount; + } +} diff --git a/src/main/java/model/AttendanceRecord.java b/src/main/java/model/AttendanceRecord.java deleted file mode 100644 index 166ba5e1a..000000000 --- a/src/main/java/model/AttendanceRecord.java +++ /dev/null @@ -1,26 +0,0 @@ -package model; - -import java.time.LocalTime; - -public class AttendanceRecord { - private final LocalTime attendanceTime; - private final AttendanceStatus attendanceStatus; - - public AttendanceRecord(LocalTime attendanceTime, AttendanceStatus attendanceStatus) { - this.attendanceTime = attendanceTime; - this.attendanceStatus = attendanceStatus; - } - - public AttendanceRecord(AttendanceRecord other) { - this.attendanceTime = other.attendanceTime; - this.attendanceStatus = other.attendanceStatus; - } - - public LocalTime getAttendanceTime() { - return attendanceTime; - } - - public AttendanceStatus getAttendanceStatus() { - return attendanceStatus; - } -} diff --git a/src/main/java/model/AttendanceRecords.java b/src/main/java/model/AttendanceRecords.java deleted file mode 100644 index c6bc5e67d..000000000 --- a/src/main/java/model/AttendanceRecords.java +++ /dev/null @@ -1,136 +0,0 @@ -package model; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.HashMap; -import java.util.Map; - -public class AttendanceRecords { - private static final int SATURDAY = 6; - private static final int SUNDAY = 7; - private static final int CHRISTMAS = 25; - - private final Map record; - - public AttendanceRecords(Map record) { - this.record = record; - } - - public void updateAttendanceStatusByLocalDate(LocalDateTime updateLocalDateTime) { - validateHoliday(updateLocalDateTime); - validateOpeningHours(updateLocalDateTime); - AttendanceStatus newAttendanceStatus = AttendanceRuleByDay - .calculateAttendance(updateLocalDateTime); - - record.entrySet().stream() - .filter(e -> compareDayIsSame(updateLocalDateTime, e.getKey())) - .findFirst() - .ifPresentOrElse( - e -> modifyAttendanceRecord(updateLocalDateTime, e.getKey(), newAttendanceStatus), - () -> { - throw new IllegalArgumentException("[ERROR] 등록되지 않은 날짜입니다."); - } - ); - } - - public void registerAttendanceRecord(LocalDateTime localDateTime) { - validateHoliday(localDateTime); - validateOpeningHours(localDateTime); - LocalDate localDate = LocalDate.from(localDateTime); - LocalTime localTime = LocalTime.from(localDateTime); - AttendanceStatus attendanceStatus = AttendanceRuleByDay.calculateAttendance(localDateTime); - AttendanceRecord attendanceRecord = new AttendanceRecord(localTime, attendanceStatus); - record.put(localDate, attendanceRecord); - } - - public long findTotalAttendanceCount() { - return (int) record.entrySet().stream() - .filter(e -> e.getValue().getAttendanceStatus().equals(AttendanceStatus.ATTENDANCE)) - .count(); - } - - public long findTotalLateCount() { - return (int) record.entrySet().stream() - .filter(e -> e.getValue().getAttendanceStatus().equals(AttendanceStatus.LATE)) - .count(); - } - - public long findTotalAbsentCount() { - return (int) record.entrySet().stream() - .filter(e -> e.getValue().getAttendanceStatus().equals(AttendanceStatus.ABSENT)) - .count(); - } - - public void createAttendanceRecords(LocalDateTime today) { - AttendanceStatus attendanceStatus = AttendanceRuleByDay.calculateAttendance(today); - record.put(LocalDate.from(today), new AttendanceRecord(LocalTime.from(today), attendanceStatus)); - } - - public void updateStateNotExistInFile(LocalDate today) { - LocalDateTime standard = LocalDateTime.of(2024, 12, 1, 0, 0); - Map recordClone = makeRecordClone(); - while (!compareDayIsSame(standard, LocalDate.from(today))) { - if (isExistLocalDate(recordClone, standard)) { - standard = standard.plusDays(1); - continue; - } - addAbsentRecordForStudent(standard); - standard = standard.plusDays(1); - } - } - - public AttendanceStatus findAttendanceStatusByLocalDateTime(LocalDateTime localDateTime) { - return record.entrySet().stream() - .filter(e -> compareDayIsSame(localDateTime, e.getKey())) - .map(e -> e.getValue().getAttendanceStatus()) - .findFirst().orElse(null); - } - - private void modifyAttendanceRecord(LocalDateTime updateLocalDateTime, LocalDate recordLocalDate, - AttendanceStatus newAttendanceStatus) { - LocalTime localTime = LocalTime.from(updateLocalDateTime); - record.put(recordLocalDate, new AttendanceRecord(localTime, newAttendanceStatus)); - } - - private void addAbsentRecordForStudent(LocalDateTime updateLocalDateTime) { - record.put(LocalDate.from(updateLocalDateTime), new AttendanceRecord(null, AttendanceStatus.ABSENT)); - } - - private void validateHoliday(LocalDateTime localDateTime) { - int day = localDateTime.getDayOfWeek().getValue(); - if (day == SATURDAY || day == SUNDAY || localDateTime.getDayOfMonth() == CHRISTMAS) { - throw new IllegalArgumentException("[주말 및 공휴일에는 등교일이 아닙니다]"); - } - } - - private void validateOpeningHours(LocalDateTime localDateTime) { - LocalTime localTime = LocalTime.from(localDateTime); - LocalTime startTime = LocalTime.of(8, 0); - LocalTime endTime = LocalTime.of(23, 0); - if (localTime.isBefore(startTime) || localTime.isAfter(endTime)) { - throw new IllegalArgumentException("[캠퍼스 운영 시간이 아닙니다.]"); - } - } - - private boolean compareDayIsSame(LocalDateTime localDateTime, LocalDate localDate) { - return LocalDate.from(localDateTime).equals(localDate); - } - - private boolean isExistLocalDate(Map recordClone, LocalDateTime localDateTime) { - return recordClone.keySet().stream() - .anyMatch(date -> compareDayIsSame(localDateTime, date)); - } - - private Map makeRecordClone() { - Map clonedMap = new HashMap<>(); - for (Map.Entry entry : record.entrySet()) { - clonedMap.put(entry.getKey(), new AttendanceRecord(entry.getValue())); - } - return clonedMap; - } - - public Map getRecord() { - return record; - } -} diff --git a/src/main/java/model/AttendanceRuleByDay.java b/src/main/java/model/AttendanceRuleByDay.java deleted file mode 100644 index 44fc6a3a2..000000000 --- a/src/main/java/model/AttendanceRuleByDay.java +++ /dev/null @@ -1,50 +0,0 @@ -package model; - -import java.time.DayOfWeek; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; - -public enum AttendanceRuleByDay { - - MONDAY(LocalTime.of(13, 0), DayOfWeek.MONDAY, "월요일"), - TUESDAY(LocalTime.of(10, 0), DayOfWeek.TUESDAY, "화요일"), - WEDNESDAY(LocalTime.of(10, 0), DayOfWeek.WEDNESDAY, "수요일"), - THURSDAY(LocalTime.of(10, 0), DayOfWeek.THURSDAY, "목요일"), - FRIDAY(LocalTime.of(10, 0), DayOfWeek.FRIDAY, "금요일"); - - private final LocalTime classStartTime; - private final DayOfWeek dayOfWeek; - private final String day; - - AttendanceRuleByDay(LocalTime classStartTime, DayOfWeek dayOfWeek, String day) { - this.classStartTime = classStartTime; - this.dayOfWeek = dayOfWeek; - this.day = day; - } - - public static AttendanceStatus calculateAttendance(LocalDateTime localDateTime) { - DayOfWeek day = localDateTime.getDayOfWeek(); - LocalTime arrivalTime = LocalTime.from(localDateTime); - return Arrays.stream(values()) - .filter(attendanceRule -> attendanceRule.dayOfWeek == day) - .map(attendanceRule -> attendanceRule.calculateAttendanceStatusByArrivalTime(arrivalTime)) - .findFirst() - .orElseThrow(); - } - - public static String findDayByDayOfWeekValue(DayOfWeek dayOfWeekValue) { - return Arrays.stream(values()) - .filter(attendanceRule -> attendanceRule.dayOfWeek == dayOfWeekValue) - .map(attendanceRule -> attendanceRule.day) - .findFirst() - .orElseThrow(); - } - - private AttendanceStatus calculateAttendanceStatusByArrivalTime(LocalTime arrivalTime) { - int late = (int) classStartTime.until(arrivalTime, ChronoUnit.MINUTES); - return AttendanceStatus.fromMinutesLate(late); - } - -} diff --git a/src/main/java/model/AttendanceStatus.java b/src/main/java/model/AttendanceStatus.java index 9d1b986aa..45dc9dad7 100644 --- a/src/main/java/model/AttendanceStatus.java +++ b/src/main/java/model/AttendanceStatus.java @@ -1,30 +1,50 @@ package model; -public enum AttendanceStatus { +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; - ABSENT("결석", 30), - ATTENDANCE("출석",0), - LATE("지각", 5); +public enum AttendanceStatus { + ATTENDANCE("출석", 0), + LATE("지각", 5), + ABSENT("결석", 30); - private final String state; - private final int attendanceJudgementTime; + private final String attendanceStatus; + private final int thresholdMinutes; - AttendanceStatus(String state, int attendanceJudgementTime) { - this.state = state; - this.attendanceJudgementTime = attendanceJudgementTime; + AttendanceStatus(String attendanceStatus, int thresholdMinutes) { + this.attendanceStatus = attendanceStatus; + this.thresholdMinutes = thresholdMinutes; } - public static AttendanceStatus fromMinutesLate(int minutesLate) { - if (minutesLate > ABSENT.attendanceJudgementTime) { - return ABSENT; + public static AttendanceStatus calculateAttendanceStatus(LocalDate todayDate, LocalTime attendanceTime) { + if (attendanceTime == null) { + return AttendanceStatus.ABSENT; } - if (minutesLate > LATE.attendanceJudgementTime) { - return LATE; - } - return ATTENDANCE; + LocalTime attendanceStartTime = WeeklyAttendanceSchedule.findAttendanceScheduleByLocalDate(todayDate); + return Arrays.stream(AttendanceStatus.values()) + .sorted(Comparator.comparingInt(AttendanceStatus::getThresholdMinutes).reversed()) + .filter(status -> attendanceTime.isAfter(attendanceStartTime.plusMinutes(status.getThresholdMinutes()))) + .findFirst() + .orElse(AttendanceStatus.ATTENDANCE); + } + + public static long calculateAttendanceStatusCount(Map attendanceRecord, + AttendanceStatus attendanceStatus) { + return attendanceRecord.entrySet().stream() + .filter(entry -> AttendanceStatus.calculateAttendanceStatus( + entry.getKey(), + entry.getValue()).equals(attendanceStatus)) + .count(); + } + + public String getAttendanceStatus() { + return attendanceStatus; } - public String getState() { - return state; + public int getThresholdMinutes() { + return thresholdMinutes; } } diff --git a/src/main/java/model/AttendanceTimeRecord.java b/src/main/java/model/AttendanceTimeRecord.java new file mode 100644 index 000000000..13f8f6f76 --- /dev/null +++ b/src/main/java/model/AttendanceTimeRecord.java @@ -0,0 +1,38 @@ +package model; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AttendanceTimeRecord { + private final Map attendanceTimeRecords; + + public AttendanceTimeRecord(List localDateTimes) { + Map map = new HashMap<>(); + for (LocalDateTime localDateTime : localDateTimes) { + map.put(LocalDate.from(localDateTime), LocalTime.from(localDateTime)); + } + this.attendanceTimeRecords = map; + } + + public void putAttendanceTimeRecord(LocalDate localDate, LocalTime localTime){ + attendanceTimeRecords.put(localDate, localTime); + } + + public boolean checkAttendanceRecordByLocalDate(LocalDate localDate) { + return attendanceTimeRecords.get(localDate) != null; + } + + public void validateDuplicateAttendance(LocalDate today) { + if (checkAttendanceRecordByLocalDate(today)) { + throw new IllegalArgumentException("[ERROR] 출석기록이 존재합니다."); + } + } + + public Map getAttendanceTimeRecords() { + return attendanceTimeRecords; + } +} diff --git a/src/main/java/model/CampusOperatingHours.java b/src/main/java/model/CampusOperatingHours.java new file mode 100644 index 000000000..0ad496e37 --- /dev/null +++ b/src/main/java/model/CampusOperatingHours.java @@ -0,0 +1,24 @@ +package model; + +import java.time.LocalTime; + +public enum CampusOperatingHours { + OPEN(LocalTime.of(8,0)), + CLOSE(LocalTime.of(23,0)); + + private final LocalTime time; + + CampusOperatingHours(LocalTime time) { + this.time = time; + } + + public static void validateOperatingHours(LocalTime time) { + if (time.isBefore(OPEN.getTime()) || time.isAfter(CLOSE.getTime())) { + throw new IllegalArgumentException("[ERROR] 캠퍼스 운영 시간이 아닙니다. 입력값: " + time); + } + } + + public LocalTime getTime() { + return time; + } +} diff --git a/src/main/java/model/Holiday.java b/src/main/java/model/Holiday.java new file mode 100644 index 000000000..a60823bc8 --- /dev/null +++ b/src/main/java/model/Holiday.java @@ -0,0 +1,46 @@ +package model; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.format.TextStyle; +import java.util.Arrays; +import java.util.Locale; + +public enum Holiday { + SATURDAY(DayOfWeek.SATURDAY), + SUNDAY(DayOfWeek.SUNDAY), + CHRISTMAS(LocalDate.of(2024,12,25)); + + private final DayOfWeek dayOfWeek; + private final LocalDate holidayDate; + + Holiday(DayOfWeek dayOfWeek) { + this.dayOfWeek = dayOfWeek; + this.holidayDate = null; + } + + Holiday(LocalDate holidayDate) { + this.dayOfWeek = null; + this.holidayDate = holidayDate; + } + public static boolean checkHoliday(LocalDate date) { + if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY) || date.getDayOfWeek().equals(DayOfWeek.SUNDAY)) { + return true; + } + + return Arrays.stream(Holiday.values()) + .anyMatch(holiday -> holiday.holidayDate != null && holiday.holidayDate.equals(date)); + } + + public static void validateHoliday(LocalDate localDate) { + if (checkHoliday(localDate)){ + throw new IllegalArgumentException(String.format("[ERROR] %d월 %d일 %s은 등교일이 아닙니다.", + localDate.getMonthValue(), + localDate.getDayOfMonth(), + localDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.KOREA))); + } + } + + + +} diff --git a/src/main/java/model/Student.java b/src/main/java/model/Student.java index 851dc6f37..3733e99fd 100644 --- a/src/main/java/model/Student.java +++ b/src/main/java/model/Student.java @@ -1,64 +1,90 @@ package model; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.HashMap; +import java.util.List; import java.util.Map; public class Student { - private static final int LATE_CONVERSION_RATE = 3; + private static final int LATE_COUNT_FOR_ONE_ABSENCE = 3; - private final AttendanceRecords attendanceRecords; private final String name; - private final AttendanceCount attendanceCount; + private final AttendanceTimeRecord attendanceTimeRecord; - public Student(String name, AttendanceRecords attendanceRecords) { - this.attendanceRecords = attendanceRecords; + public Student(String name, List localDateTime) { + this.attendanceTimeRecord = new AttendanceTimeRecord(localDateTime); this.name = name; - this.attendanceCount = new AttendanceCount(createAttendanceCount()); } - public void modifyAttendanceRecord(LocalDateTime modifyDateTime) { - attendanceRecords.updateAttendanceStatusByLocalDate(modifyDateTime); + public LocalTime findAttendanceLocalTimeByLocalDate(LocalDate localDate) { + return attendanceTimeRecord.getAttendanceTimeRecords().get(localDate); } - public void attendanceRegister(LocalDateTime localDateTime) { - attendanceRecords.registerAttendanceRecord(localDateTime); + public void registerAttendanceRecord(LocalDate todayDate, LocalTime attendanceTime) { + attendanceTimeRecord.validateDuplicateAttendance(todayDate); + CampusOperatingHours.validateOperatingHours(attendanceTime); + attendanceTimeRecord.putAttendanceTimeRecord(todayDate, attendanceTime); } - public void createAttendanceRecords(LocalDateTime localDateTime) { - attendanceRecords.createAttendanceRecords(localDateTime); + public void modifyAttendanceRecord(int modifyDate, LocalTime modifyTime) { + LocalDate localDate = LocalDate.of(2024, 12, modifyDate); + CampusOperatingHours.validateOperatingHours(modifyTime); + attendanceTimeRecord.putAttendanceTimeRecord(localDate, modifyTime); } - public String findStateByLocalDateTime(LocalDateTime localDateTime) { - return attendanceRecords.findAttendanceStatusByLocalDateTime(localDateTime).getState(); + public void updateNonAttendanceRecordStatusIsAbsent(LocalDate today) { + LocalDate startDate = LocalDate.of(today.getYear(), 12, 1); + LocalDate endDate = today.minusDays(1); + + for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { + if (Holiday.checkHoliday(date)) { + continue; + } + registerAsAbsentIfNoAttendance(date); + } + } + + public long calculateTotalAbsentCount() { + return calculateAbsentCount() + calculateLateCount() / LATE_COUNT_FOR_ONE_ABSENCE; + } + + public long calculateAbsentCount() { + return AttendanceStatus.calculateAttendanceStatusCount(getAttendanceTimeRecords(), AttendanceStatus.ABSENT); } - public long calculateAbsent() { - updateAttendanceTotalCount(); - return attendanceCount.getAbsentTotalCount() + attendanceCount.getLateTotalCount() / LATE_CONVERSION_RATE; + public long calculateLateCount() { + return AttendanceStatus.calculateAttendanceStatusCount(getAttendanceTimeRecords(), AttendanceStatus.LATE); } - public void updateAttendanceTotalCount(){ - attendanceCount.updateAttendanceCount(attendanceRecords); + public boolean isAtRiskOfCounselingOrExpulsion() { + AttendancePenalty attendancePenalty = AttendancePenalty.findPenaltyByAbsentCount(calculateTotalAbsentCount()); + return attendancePenalty.equals(AttendancePenalty.COUNSELING) || + attendancePenalty.equals(AttendancePenalty.EXPULSION); } - private Map createAttendanceCount(){ - Map attendanceCount = new HashMap<>(); - for (AttendanceStatus attendanceStatus : AttendanceStatus.values()){ - attendanceCount.put(attendanceStatus, 0L); + public Map getAttendanceStatusCount() { + Map attendanceStatusCount = new HashMap<>(); + for (AttendanceStatus attendanceStatus : AttendanceStatus.values()) { + attendanceStatusCount.put( + attendanceStatus, + AttendanceStatus.calculateAttendanceStatusCount(getAttendanceTimeRecords(), attendanceStatus)); } - return attendanceCount; + return attendanceStatusCount; } - public AttendanceCount getAttendanceCount() { - return attendanceCount; + private void registerAsAbsentIfNoAttendance(LocalDate date) { + if (!attendanceTimeRecord.checkAttendanceRecordByLocalDate(date)) { + attendanceTimeRecord.putAttendanceTimeRecord(date, null); + } } - public String getName() { - return name; + public Map getAttendanceTimeRecords() { + return attendanceTimeRecord.getAttendanceTimeRecords(); } - public AttendanceRecords getAttendanceRecords() { - return attendanceRecords; + public String getName() { + return name; } -} +} \ No newline at end of file diff --git a/src/main/java/model/StudentPunishment.java b/src/main/java/model/StudentPunishment.java deleted file mode 100644 index 359669b99..000000000 --- a/src/main/java/model/StudentPunishment.java +++ /dev/null @@ -1,17 +0,0 @@ -package model; - -public enum StudentPunishment { - WARNING(2), - INTERVIEW(3), - DISMISSAL(5); - - private final int absenceLimit; - - StudentPunishment(int absenceLimit) { - this.absenceLimit = absenceLimit; - } - - public int getAbsenceLimit() { - return absenceLimit; - } -} diff --git a/src/main/java/model/WeeklyAttendanceSchedule.java b/src/main/java/model/WeeklyAttendanceSchedule.java new file mode 100644 index 000000000..352e82657 --- /dev/null +++ b/src/main/java/model/WeeklyAttendanceSchedule.java @@ -0,0 +1,42 @@ +package model; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.TextStyle; +import java.util.Arrays; +import java.util.Locale; + +public enum WeeklyAttendanceSchedule { + MONDAY(LocalTime.of(13, 0), DayOfWeek.MONDAY), + TUESDAY(LocalTime.of(10, 0), DayOfWeek.TUESDAY), + WEDNESDAY(LocalTime.of(10, 0), DayOfWeek.WEDNESDAY), + THURSDAY(LocalTime.of(10, 0), DayOfWeek.THURSDAY), + FRIDAY(LocalTime.of(10, 0), DayOfWeek.FRIDAY); + + private final LocalTime attendanceStartTime; + private final DayOfWeek dayOfWeek; + + WeeklyAttendanceSchedule(LocalTime attendanceStartTime, DayOfWeek dayOfWeek) { + this.attendanceStartTime = attendanceStartTime; + this.dayOfWeek = dayOfWeek; + } + + public static LocalTime findAttendanceScheduleByLocalDate(LocalDate localDate) { + Holiday.validateHoliday(localDate); + return Arrays.stream(WeeklyAttendanceSchedule.values()) + .filter(weeklyAttendanceSchedule -> weeklyAttendanceSchedule.dayOfWeek.equals(localDate.getDayOfWeek())) + .map(WeeklyAttendanceSchedule::getAttendanceStartTime) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException( + String.format("[ERROR] 입력받은 %d월 %d일 %s의 attendanceStartTime을 찾지 못하였습니다.", + localDate.getMonthValue(), + localDate.getDayOfMonth(), + localDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.KOREA))) + ); + } + + public LocalTime getAttendanceStartTime() { + return attendanceStartTime; + } +} \ No newline at end of file diff --git a/src/main/java/util/AttendanceRecordFormatter.java b/src/main/java/util/AttendanceRecordFormatter.java new file mode 100644 index 000000000..a631263de --- /dev/null +++ b/src/main/java/util/AttendanceRecordFormatter.java @@ -0,0 +1,44 @@ +package util; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.util.Locale; +import model.AttendancePenalty; +import model.AttendanceStatus; +import model.Student; + +public class AttendanceRecordFormatter { + public static String attendanceRecordFormatter(Student student, LocalDate localDate) { + LocalTime localTime = student.findAttendanceLocalTimeByLocalDate(localDate); + AttendanceStatus attendanceStatus = AttendanceStatus.calculateAttendanceStatus(localDate, localTime); + String attendanceTime = localTimeFormatter(localTime); + return localDate.getMonthValue() + "월 " + + localDate.getDayOfMonth() + "일 " + + localDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.KOREA) + " " + + attendanceTime + + " (" + attendanceStatus.getAttendanceStatus() + ")"; + } + + public static String expulsionRiskRecordFormatter(Student student) { + long absent = student.calculateAbsentCount(); + long late = student.calculateLateCount(); + long totalAbsent = student.calculateTotalAbsentCount(); + AttendancePenalty attendancePenalty = AttendancePenalty.findPenaltyByAbsentCount(totalAbsent); + return String.format("- %s: 결석 %d회, 지각 %d회 (%s)", + student.getName(), + absent, + late, + attendancePenalty.getPenalty()); + } + + private static String localTimeFormatter(LocalTime localTime) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); + if (localTime == null) { + return "--:--"; + } + return localTime.format(formatter); + } +} diff --git a/src/main/java/util/FileInformationProvider.java b/src/main/java/util/FileInformationProvider.java new file mode 100644 index 000000000..70c43d2f1 --- /dev/null +++ b/src/main/java/util/FileInformationProvider.java @@ -0,0 +1,35 @@ +package util; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class FileInformationProvider { + public static Map> loadStudentAttendance() throws IOException { + List fileAttendanceRecord = attendanceRecordReader(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + return fileAttendanceRecord.stream() + .map(record -> record.split(",")) + .collect(Collectors.groupingBy( + (arr -> arr[0]), + Collectors.mapping(arr -> LocalDateTime.parse(arr[1], formatter), Collectors.toList()) + )); + } + + private static List attendanceRecordReader() throws IOException { + String filePath = "src/main/resources/attendances.csv"; + try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { + return br.lines() + .skip(1) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new IOException("[ERROR] 파일을 읽는 중 오류가 발생했습니다.", e); + } + } +} diff --git a/src/main/java/util/FileInput.java b/src/main/java/util/FileInput.java deleted file mode 100644 index 46f5e49d2..000000000 --- a/src/main/java/util/FileInput.java +++ /dev/null @@ -1,93 +0,0 @@ -package util; - -import constant.DateFormatInformation; -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import model.AttendanceRecord; -import model.AttendanceRecords; -import model.AttendanceRuleByDay; -import model.AttendanceStatus; -import model.Student; - -public class FileInput { - - private final String filePath = "src/main/resources/attendances.csv"; - private final BufferedReader fileBr; - - public FileInput() throws IOException { - fileBr = new BufferedReader(new FileReader(filePath)); - } - - public List createStudents() throws IOException { - List readAttendanceFile = readAttendanceFile(); - List students = new ArrayList<>(); - - for (String information : readAttendanceFile) { - String[] studentNameAndAttendanceTime = information.split(","); - createStudentByFileInfo(students, studentNameAndAttendanceTime); - } - return students; - } - - private List readAttendanceFile() throws IOException { - - try (BufferedReader br = fileBr) { - return br.lines() - .skip(1) - .collect(Collectors.toList()); - } catch (IOException e) { - throw new IOException("[ERROR] 파일을 읽는 중 오류가 발생했습니다.", e); - } - } - - private void createStudentByFileInfo(List students, String[] studentNameAndAttendanceTime) { - String name = studentNameAndAttendanceTime[0]; - LocalDateTime timeInformation = makeLocalDateTimeFromString(studentNameAndAttendanceTime[1]); - if (findStudentByName(students, name) == null) { - Student student = new Student(name, createAttendanceRecords(timeInformation)); - students.add(student); - makeDateTimeFormatAndUpdateStudentState(student, timeInformation); - return; - } - Student student = findStudentByName(students, name); - makeDateTimeFormatAndUpdateStudentState(student, timeInformation); - } - - private Student findStudentByName(List students, String name) { - return students.stream() - .filter(s -> s.getName().equals(name)) - .findFirst() - .orElse(null); - } - - private static LocalDateTime makeLocalDateTimeFromString(String timeInformation) { - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern( - DateFormatInformation.LOCAL_DATE_TIME_FORMATTER); - return LocalDateTime.parse(timeInformation, dateTimeFormatter); - } - - private static void makeDateTimeFormatAndUpdateStudentState(Student student, LocalDateTime localDateTime) { - student.createAttendanceRecords(localDateTime); - } - - private static AttendanceRecords createAttendanceRecords(LocalDateTime localDateTime) { - Map attendanceRecords = new HashMap<>(); - LocalDate localDate = LocalDate.from(localDateTime); - LocalTime localTime = LocalTime.from(localDateTime); - AttendanceStatus attendanceStatus = AttendanceRuleByDay.calculateAttendance(localDateTime); - AttendanceRecord attendanceRecord = new AttendanceRecord(localTime, attendanceStatus); - attendanceRecords.put(localDate, attendanceRecord); - return new AttendanceRecords(attendanceRecords); - } - -} diff --git a/src/main/java/util/LocalDateTimePrintFormatter.java b/src/main/java/util/LocalDateTimePrintFormatter.java deleted file mode 100644 index 3beecc93f..000000000 --- a/src/main/java/util/LocalDateTimePrintFormatter.java +++ /dev/null @@ -1,26 +0,0 @@ -package util; - -import constant.DateFormatInformation; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.TextStyle; -import java.util.Locale; -import model.AttendanceRecord; - -public class LocalDateTimePrintFormatter { - - public static String LocalDateTimeToLocalTime(LocalDate localDate, AttendanceRecord attendanceRecord) { - if (attendanceRecord.getAttendanceTime() == null) { - DayOfWeek dayOfWeek = localDate.getDayOfWeek(); - return localDate.format(DateTimeFormatter.ofPattern("MM월 dd일 " + dayOfWeek.getDisplayName( - TextStyle.FULL, Locale.KOREAN) + " --:--")); - } - - LocalDateTime localDateTime = LocalDateTime.of(localDate, attendanceRecord.getAttendanceTime()); - DayOfWeek dayOfWeek = localDateTime.getDayOfWeek(); - return localDateTime.format(DateTimeFormatter.ofPattern("MM월 dd일 " + dayOfWeek.getDisplayName( - TextStyle.FULL, Locale.KOREAN) + " " + DateFormatInformation.LOCAL_TIME_FORMATTER)); - } -} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 5675bccd3..d5a356cab 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,118 +1,45 @@ package view; -import constant.DateFormatInformation; -import java.time.DateTimeException; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalDateTime; +import constant.MenuOption; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.Scanner; -import model.AttendanceRuleByDay; public class InputView { - private final static String ATTENDANCE_CHECK_MENU = "1. 출석 확인"; - private final static String ATTENDANCE_MODIFY_MENU = "2. 출석 수정"; - private final static String RECORD_PRINT_FOR_EACH_CREW_MENU = "3. 크루별 출석 기록 확인"; - private final static String DISMISSAL_CREW_CHECK_MENU = "4. 제적 위험자 확인"; - private final static String QUIT = "Q. 종료"; - private final static String PROMPT_TIME_INPUT_TO_MODIFY = "언제로 변경하겠습니까?"; - private final static String PROMPT_DAY_INPUT_TO_MODIFY = "수정하려는 날짜(일)를 입력해 주세요."; - private final static String PROMPT_STUDENT_NAME_INPUT_TO_MODIFY = "출석을 수정하려는 크루의 닉네임을 입력해 주세요."; - private final static String PROMPT_STUDENT_NAME_INPUT = "닉네임을 입력해 주세요."; - private final static String PROMPT_START_TIME_INPUT = "등교 시간을 입력해 주세요."; - private final static String MENU_OPTION = "[1-4]|Q"; - private final static int CAMPUS_START_TIME = 8; - private final static int CAMPUS_END_TIME = 23; - private final static int START_DATE = 1; - private final static int END_DATE = 31; + static Scanner SCANNER = new Scanner(System.in); - - - private final static Scanner scanner = new Scanner(System.in); - - public static void printTodayAndSelectFunction(LocalDate localDate) { - int month = localDate.getMonthValue(); - int date = localDate.getDayOfMonth(); - DayOfWeek dayOfWeek = localDate.getDayOfWeek(); - String day = AttendanceRuleByDay.findDayByDayOfWeekValue(dayOfWeek); - System.out.println("오늘은 " + month + "월 " + date + "일 " + day + "입니다. 기능을 선택해 주세요."); - } - - public static String userInput() { - return scanner.nextLine(); - } - - public static String getUserInputString() { - printMenu(); - String input = userInput(); - try { - return isQOrOneOrTwoOrThreeOrFour(input); - } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); - return getUserInputString(); - } - } - - public static void printInputNicName() { - System.out.println(PROMPT_STUDENT_NAME_INPUT); + public static MenuOption inputChooseFunctionOption() { + return MenuOption.validateSelectMenuOption(SCANNER.nextLine()); } - public static void printStartTime() { - System.out.println(PROMPT_START_TIME_INPUT); + public static String input() { + return SCANNER.nextLine(); } - public static void printStudentNameForModify() { - System.out.println(PROMPT_STUDENT_NAME_INPUT_TO_MODIFY); + public static LocalTime inputAttendanceTime() { + String input = SCANNER.nextLine(); + return validateTimeFormat(input); } - public static int inputDateForModify() { - System.out.println(PROMPT_DAY_INPUT_TO_MODIFY); + public static LocalTime validateTimeFormat(String time) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); try { - int date = Integer.parseInt(userInput()); - if (date < START_DATE || date > END_DATE) { - throw new IllegalArgumentException("[ERROR] 1~31 사이의 숫자만 입력해주세요"); - } - return date; - } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); - return inputDateForModify(); + return LocalTime.parse(time, formatter); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("[ERROR] 잘못된 시간 형식입니다. 'HH:mm' 형식으로 입력하세요."); } } - public static void printTimeForModify() { - System.out.println(PROMPT_TIME_INPUT_TO_MODIFY); - } - - public static LocalDateTime makeLocalDateToLocalDateTime(LocalDate localDate) { - String time = userInput(); + public static int validateDateFormat(String input) { try { - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern( - DateFormatInformation.LOCAL_TIME_FORMATTER); - LocalTime localTime = LocalTime.parse(time, dateTimeFormatter); - return localDate.atTime(localTime); - } catch (DateTimeException e) { - throw new IllegalArgumentException("[ERROR] 시간 형식에 맞지 않습니다."); - } - } - - public static void isNotOpeningHour(LocalDateTime localDateTime) { - if (localDateTime.getHour() < CAMPUS_START_TIME || localDateTime.getHour() >= CAMPUS_END_TIME) { - throw new IllegalArgumentException("[ERROR] 캠퍼스 운영 시간이 아닙니다."); - } - } - private static void printMenu() { - System.out.println(ATTENDANCE_CHECK_MENU); - System.out.println(ATTENDANCE_MODIFY_MENU); - System.out.println(RECORD_PRINT_FOR_EACH_CREW_MENU); - System.out.println(DISMISSAL_CREW_CHECK_MENU); - System.out.println(QUIT); - } - - private static String isQOrOneOrTwoOrThreeOrFour(String input) { - if (!input.matches(MENU_OPTION)) { - throw new IllegalArgumentException("[ERROR] 메뉴에 없는 선택지 입니다."); + int a = Integer.parseInt(input); + if (a < 1 || a > 31) { + throw new IllegalArgumentException("[ERROR] 날짜 입력은 1이상 31이하 숫자만 입력 가능합니다"); + } + return a; + } catch (NumberFormatException n) { + throw new IllegalArgumentException("[ERROR] 날짜 입력은 1이상 31이하 숫자만 입력 가능합니다"); } - return input; } } diff --git a/src/main/java/view/OutPutView.java b/src/main/java/view/OutPutView.java new file mode 100644 index 000000000..4b1ec05e1 --- /dev/null +++ b/src/main/java/view/OutPutView.java @@ -0,0 +1,87 @@ +package view; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.TextStyle; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import model.AttendancePenalty; +import model.AttendanceStatus; +import model.Student; +import util.AttendanceRecordFormatter; + +public class OutPutView { + public static void displayAttendanceMenu(LocalDate localDate) { + System.out.printf("오늘은 %d월 %d일 %s입니다. 기능을 선택해 주세요\n", + localDate.getMonthValue(), + localDate.getDayOfMonth(), + localDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.KOREA)); + System.out.println( + "1. 출석 확인\n" + + "2. 출석 수정\n" + + "3. 크루별 출석 기록 확인\n" + + "4. 제적 위험자 확인\n" + + "Q. 종료\n"); + } + + public static void requestNickName() { + System.out.println("닉네임을 입력해 주세요."); + } + + public static void requestAttendanceTime() { + System.out.println("등교 시간을 입력해 주세요."); + } + + public static void requestModifyNickName() { + System.out.println("출석을 수정하려는 크루의 닉네임을 입력해 주세요."); + } + + public static void requestModifyDate() { + System.out.println("수정하려는 날짜(일)를 입력해 주세요."); + } + + public static void requestModifyTime() { + System.out.println("언제로 변경하겠습니까?"); + } + + public static void displayModifyAttendanceRecord(String beforeRecord, String modifyRecord) { + System.out.println(beforeRecord + "->" + modifyRecord + "수정 완료!"); + } + + public static void displayRegisterAttendanceRecord(String record) { + System.out.println(record); + } + + public static void displayTotalAttendanceRecord(Student student) { + Map timeRecord = student.getAttendanceTimeRecords(); + System.out.printf("이번 달 %s의 출석 기록입니다.\n", student.getName()); + for (LocalDate localDate : timeRecord.keySet()) { + System.out.println(AttendanceRecordFormatter.attendanceRecordFormatter( + student, localDate)); + } + } + + public static void displayTotalAttendanceCount(Student student) { + Map attendanceCount = student.getAttendanceStatusCount(); + System.out.println(); + for (AttendanceStatus attendanceStatus : attendanceCount.keySet()) { + System.out.println( + attendanceStatus.getAttendanceStatus() + " : " + attendanceCount.get(attendanceStatus) + "회"); + } + } + + public static void displayCounselingCandidate(Student student) { + long expulsionCount = student.calculateTotalAbsentCount(); + AttendancePenalty attendancePenalty = AttendancePenalty.findPenaltyByAbsentCount(expulsionCount); + if (!attendancePenalty.equals(AttendancePenalty.NONE)) { + System.out.println(attendancePenalty.getPenalty() + "대상자 입니다."); + } + } + + public static void displayExpulsionRiskStudents(List students) { + for (Student student : students) { + System.out.println(AttendanceRecordFormatter.expulsionRiskRecordFormatter(student)); + } + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java deleted file mode 100644 index f754e133e..000000000 --- a/src/main/java/view/OutputView.java +++ /dev/null @@ -1,110 +0,0 @@ -package view; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import model.AttendanceRecord; -import model.Student; -import model.StudentPunishment; -import model.AttendanceBook; -import util.LocalDateTimePrintFormatter; - -public class OutputView { - private static final String COUNT = "회"; - private static final String ATTENDANCE = "출석: "; - private static final String LATE = "지각: "; - private static final String ABSENT = "결석: "; - private static final String DISMISSAL_SUBJECT = "제적 대상자입니다."; - private static final String WARNING_SUBJECT = "제적 대상자입니다."; - private static final String INTERVIEW_SUBJECT = "제적 대상자입니다."; - private static final String INTERVIEW_LABEL_FORMATTER = "- %s: 결석 %d회, 지각 %d회 (면담)\n"; - private static final String WARNING_LABEL_FORMATTER = "- %s: 결석 %d회, 지각 %d회 (경고)\n"; - private static final String DISMISSAL_LABEL_FORMATTER = "- %s: 결석 %d회, 지각 %d회 (제적)\n"; - private static final String PARENTHESES_FORMATTER = "( %s )\n"; - - public static void printTodayAttendanceResult(Student student, LocalDateTime localDateTime) { - Entry findResult = student.getAttendanceRecords().getRecord().entrySet().stream() - .filter(e -> e.getKey().equals(LocalDate.from(localDateTime))) - .findFirst() - .orElseThrow(); - String dateAndTime = LocalDateTimePrintFormatter - .LocalDateTimeToLocalTime(findResult.getKey(), findResult.getValue()); - String state = findResult.getValue().getAttendanceStatus().getState(); - System.out.printf(dateAndTime); - System.out.printf(String.format(PARENTHESES_FORMATTER, state)); - } - - public static void printSecondMenu(String recordBeforeModify, String localDateTimeFormat3) { - System.out.println(recordBeforeModify + " -> " + localDateTimeFormat3); - } - - public static void printAttendanceRecord(Map record) { - List> entries = - record.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .toList(); - - for (Map.Entry entry : entries) { - System.out.printf(LocalDateTimePrintFormatter.LocalDateTimeToLocalTime(entry.getKey(), entry.getValue())); - System.out.printf(String.format(PARENTHESES_FORMATTER, entry.getValue().getAttendanceStatus().getState())); - } - } - - public static void printStudentState(Student student) { - student.updateAttendanceTotalCount(); - System.out.println(ATTENDANCE + student.getAttendanceCount().getAttendanceTotalCount() + COUNT); - System.out.println(LATE + student.getAttendanceCount().getLateTotalCount() + COUNT); - System.out.println(ABSENT + student.getAttendanceCount().getAbsentTotalCount() + COUNT); - } - - public static void printStudentPunishmentLabel(Student student) { - if (student.calculateAbsent() > StudentPunishment.DISMISSAL.getAbsenceLimit()) { - System.out.println(DISMISSAL_SUBJECT); - return; - } - if (student.calculateAbsent() >= StudentPunishment.INTERVIEW.getAbsenceLimit()) { - System.out.println(INTERVIEW_SUBJECT); - return; - } - if (student.calculateAbsent() >= StudentPunishment.WARNING.getAbsenceLimit()) { - System.out.println(WARNING_SUBJECT); - } - } - - public static void printEveryStudentPunishmentLabel(AttendanceBook studentRepository) { - for (Student student : studentRepository.getStudents()) { - printStudentPunishmentLabelAndPrint(student); - } - } - - private static void printStudentPunishmentLabelAndPrint(Student student) { - if (student.calculateAbsent() > StudentPunishment.DISMISSAL.getAbsenceLimit()) { - System.out.printf(String.format( - DISMISSAL_LABEL_FORMATTER, - student.getName(), - student.getAttendanceCount().getAbsentTotalCount(), - student.getAttendanceCount().getLateTotalCount()) - ); - return; - } - if (student.calculateAbsent() >= StudentPunishment.INTERVIEW.getAbsenceLimit()) { - System.out.printf(String.format( - INTERVIEW_LABEL_FORMATTER, - student.getName(), - student.getAttendanceCount().getAbsentTotalCount(), - student.getAttendanceCount().getLateTotalCount()) - ); - return; - } - if (student.calculateAbsent() >= StudentPunishment.WARNING.getAbsenceLimit()) { - System.out.printf(String.format( - WARNING_LABEL_FORMATTER, - student.getName(), - student.getAttendanceCount().getAbsentTotalCount(), - student.getAttendanceCount().getLateTotalCount()) - ); - } - } -} diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/java/model/AttendanceBookTest.java b/src/test/java/model/AttendanceBookTest.java index ec7689894..7647565ed 100644 --- a/src/test/java/model/AttendanceBookTest.java +++ b/src/test/java/model/AttendanceBookTest.java @@ -1,67 +1,80 @@ package model; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; -import controller.Controller; +import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; -import org.junit.jupiter.api.Assertions; +import java.time.LocalTime; +import java.util.List; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import view.InputView; +import util.FileInformationProvider; public class AttendanceBookTest { - private final AttendanceBook studentRepository = new Controller().createStudentRepository(); - @Test - @DisplayName("존재하지 않는 학생을 입력시 예외처리 한다.") - void test1() { - String input = "포비"; - assertThatThrownBy(() -> studentRepository.notExistStudent(input)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 등록되지 않은 닉네임입니다."); + @DisplayName("학생 이름으로 학생 찾기 구현 테스트") + void 특정_학생의_출석_시간_가져오기_구현() throws IOException { + String expect = "빙티"; + AttendanceBook attendanceBook = new AttendanceBook(FileInformationProvider.loadStudentAttendance()); + String result = attendanceBook.findStudentByNickName(expect).getName(); + assertThat(expect).isEqualTo(result); } @Test - @DisplayName("등교시간 잘못 입력시 예외처리 한다.") - void test2() { - assertThatThrownBy(() -> InputView.isNotOpeningHour(LocalDateTime.of(2024,12,13,7,59))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 캠퍼스 운영 시간이 아닙니다."); - } - - @Test - @DisplayName("이름 기준으로 학생 객체 찾는 기능") - void test3() { - String name = "짱수"; - Student student = studentRepository.findStudentByName(name); - String expectStudentName = student.getName(); - assertThat(name).isEqualTo(expectStudentName); + @DisplayName("제적 위험자 조건에 맞는 크루 찾기 테스트") + void 재적_위험자_조건에_맞는_크루_찾기_테스트() { + List students = List.of( + new Student("빙티", List.of( + LocalDateTime.of(2024, 12, 13, 10, 31), + LocalDateTime.of(2024, 12, 12, 10, 31), + LocalDateTime.of(2024, 12, 11, 10, 31) + )), + new Student("이든", List.of( + LocalDateTime.of(2024, 12, 13, 10, 31), + LocalDateTime.of(2024, 12, 12, 10, 31), + LocalDateTime.of(2024, 12, 11, 10, 31) + )), + new Student("쿠키", List.of( + LocalDateTime.of(2024, 12, 13, 10, 0), + LocalDateTime.of(2024, 12, 12, 10, 0), + LocalDateTime.of(2024, 12, 11, 10, 0) + )) + ); + AttendanceBook attendanceBook = new AttendanceBook(students); + List expulsionRiskStudents = attendanceBook.findExpulsionRiskStudents(); + Assertions.assertThat(expulsionRiskStudents) + .extracting(Student::getName) + .contains("빙티", "이든"); } @Test - @DisplayName("등교 시간을 바탕으로 출석 기록 업데이트하는 기능") - void test4() { - String name = "짱수"; - LocalDateTime localDateTime = LocalDateTime.of(2024, 12, 13,9,59); - - Student student = studentRepository.findStudentByName(name); - student.attendanceRegister(localDateTime); - student.updateAttendanceTotalCount(); - assertThat(student.getAttendanceCount().getAttendanceTotalCount()).isEqualTo(3); + @DisplayName("출결 기록이 없는 경우 null 처리 테스트") + void 출결_기록이_없는_경우_null_처리_테스트() throws IOException { + List students = List.of( + new Student("빙티", List.of( + LocalDateTime.of(2024, 12, 13, 10, 31), + LocalDateTime.of(2024, 12, 12, 10, 31), + LocalDateTime.of(2024, 12, 11, 10, 31) + )), + new Student("이든", List.of( + LocalDateTime.of(2024, 12, 13, 10, 31), + LocalDateTime.of(2024, 12, 12, 10, 31), + LocalDateTime.of(2024, 12, 11, 10, 31) + )), + new Student("쿠키", List.of( + LocalDateTime.of(2024, 12, 13, 10, 0), + LocalDateTime.of(2024, 12, 12, 10, 0), + LocalDateTime.of(2024, 12, 11, 10, 0) + )) + ); + AttendanceBook attendanceBook = new AttendanceBook(students); + attendanceBook.updateNonExistentAttendanceRecords(LocalDate.of(2024,12,15)); + Student student = attendanceBook.findStudentByNickName("이든"); + LocalTime result = student.findAttendanceLocalTimeByLocalDate(LocalDate.of(2024,12,14)); + assertNull(result); } - - @Test - @DisplayName("출석 기록 업데이트 하는 메서드 테스트") - void test7() { - Student student1 = studentRepository.findStudentByName("빙티"); - student1.modifyAttendanceRecord(LocalDateTime.of(2024,12,3,10,0)); - Assertions.assertEquals(student1.getAttendanceRecords() - .getRecord() - .get(LocalDate.of(2024, 12, 3)) - .getAttendanceStatus(), AttendanceStatus.ATTENDANCE); - } - } diff --git a/src/test/java/model/AttendanceCountTest.java b/src/test/java/model/AttendanceCountTest.java deleted file mode 100644 index e47561fc6..000000000 --- a/src/test/java/model/AttendanceCountTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package model; - -import controller.Controller; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class AttendanceCountTest { - private final AttendanceBook studentRepository = new Controller().createStudentRepository(); - - @Test - @DisplayName("총 출석 횟수 업데이트 기능") - void test1(){ - Student student = studentRepository.findStudentByName("빙티"); - long expect = 1; - student.updateAttendanceTotalCount(); - long result = student.getAttendanceCount().getAttendanceTotalCount(); - - Assertions.assertEquals(expect, result); - } - -} \ No newline at end of file diff --git a/src/test/java/model/AttendancePenaltyTest.java b/src/test/java/model/AttendancePenaltyTest.java new file mode 100644 index 000000000..d1315ff1b --- /dev/null +++ b/src/test/java/model/AttendancePenaltyTest.java @@ -0,0 +1,29 @@ +package model; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AttendancePenaltyTest { + + @Test + @DisplayName("결석 횟수에 따른 면담 값 가져오기 테스트") + void 결석_횟수에_따른_면담_상태_가져오기_테스트(){ + int count = 5; + AttendancePenalty expect = AttendancePenalty.COUNSELING; + AttendancePenalty result = AttendancePenalty.findPenaltyByAbsentCount(count); + assertEquals(expect, result); + } + + @Test + @DisplayName("결석 횟수에 따른 제적 패널티 값 가져오기 테스트") + void 결석_횟수에_따른_제적_상태_가져오기_테스트(){ + int count = 6; + AttendancePenalty expect = AttendancePenalty.EXPULSION; + AttendancePenalty result = AttendancePenalty.findPenaltyByAbsentCount(count); + assertEquals(expect, result); + } + + +} \ No newline at end of file diff --git a/src/test/java/model/AttendanceRecordsTest.java b/src/test/java/model/AttendanceRecordsTest.java deleted file mode 100644 index 6c351f1e6..000000000 --- a/src/test/java/model/AttendanceRecordsTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package model; - -import static org.junit.jupiter.api.Assertions.*; - -import controller.Controller; -import java.time.LocalDate; -import java.time.LocalDateTime; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class AttendanceRecordsTest { - - private final AttendanceBook studentRepository = new Controller().createStudentRepository(); - private final Student student = studentRepository.findStudentByName("빙티"); - - @BeforeEach - void studentRepositoryUpdate(){ - student.getAttendanceRecords().updateStateNotExistInFile(LocalDate.of(2024,12,13)); - } - - @Test - void findTotalAttendanceCount() { - long expect = 1; - long result = student.getAttendanceRecords().findTotalAttendanceCount(); - assertEquals(expect, result); - } - - @Test - void findTotalLateCount() { - long expect = 1; - long result = student.getAttendanceRecords().findTotalLateCount(); - assertEquals(expect, result); - } - - @Test - void findTotalAbsentCount() { - long expect = 10; - long result = student.getAttendanceRecords().findTotalAbsentCount(); - assertEquals(expect, result); - } - - @Test - void createAttendanceRecords() { - LocalDateTime today = LocalDateTime.of(2024, 12, 13, 9,59); - student.createAttendanceRecords(today); - AttendanceStatus expect = AttendanceStatus.ATTENDANCE; - AttendanceStatus result = student.getAttendanceRecords().findAttendanceStatusByLocalDateTime(today); - - assertEquals(expect, result); - } - - @Test - void updateStateNotExistInFile() { - student.getAttendanceRecords().updateStateNotExistInFile(LocalDate.of(2024,12,13)); - student.getAttendanceCount().updateAttendanceCount(student.getAttendanceRecords()); - long expect = 10; - long result = student.getAttendanceCount().getAbsentTotalCount(); - - assertEquals(expect, result); - } - - @Test - void findAttendanceStatusByLocalDateTime() { - AttendanceStatus expect = AttendanceStatus.ABSENT; - AttendanceStatus result = student.getAttendanceRecords() - .findAttendanceStatusByLocalDateTime(LocalDateTime.of(2024,12,11,9,9)); - assertEquals(expect, result); - } - -} \ No newline at end of file diff --git a/src/test/java/model/AttendanceRuleByDayTest.java b/src/test/java/model/AttendanceRuleByDayTest.java deleted file mode 100644 index 8fcf1d0f6..000000000 --- a/src/test/java/model/AttendanceRuleByDayTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package model; - -import java.time.DayOfWeek; -import java.time.LocalDateTime; -import java.time.LocalTime; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class AttendanceRuleByDayTest { - - @Test - @DisplayName("요일과 시간을 기준으로 model.AttendanceStatus 객체 생성 테스트 (월요일 출석)") - void test1() { - Assertions.assertThat(AttendanceRuleByDay.calculateAttendance(LocalDateTime.of(2024, 12, 2, 13, 0))) - .isEqualTo(AttendanceStatus.ATTENDANCE); - } - - @Test - @DisplayName("요일과 시간을 기준으로 model.AttendanceStatus 객체 생성 테스트 (금요일 결석)") - void test2() { - Assertions.assertThat(AttendanceRuleByDay.calculateAttendance(LocalDateTime.of(2024, 12, 5, 10, 31))) - .isEqualTo(AttendanceStatus.ABSENT); - } - - @Test - @DisplayName("정수형을 입력 받고, String 형 요일을 출력하는 메서드 테스트") - void test3() { - DayOfWeek friday = DayOfWeek.FRIDAY; - Assertions.assertThat(AttendanceRuleByDay. - findDayByDayOfWeekValue(friday)). - isEqualTo("금요일"); - } - - @Test - void calculateAttendance() { - AttendanceStatus expect = AttendanceStatus.LATE; - AttendanceStatus result = AttendanceRuleByDay - .calculateAttendance(LocalDateTime.of(2024,12,13,10,6)); - Assertions.assertThat(result).isEqualTo(expect); - } - - @Test - void findDayByDayOfWeekValue() { - String expect = "월요일"; - String result = AttendanceRuleByDay.findDayByDayOfWeekValue(DayOfWeek.MONDAY); - Assertions.assertThat(result).isEqualTo(expect); - } -} \ No newline at end of file diff --git a/src/test/java/model/AttendanceStatusTest.java b/src/test/java/model/AttendanceStatusTest.java new file mode 100644 index 000000000..ea1270607 --- /dev/null +++ b/src/test/java/model/AttendanceStatusTest.java @@ -0,0 +1,95 @@ +package model; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class AttendanceStatusTest { + @Test + @DisplayName("월요일을 제외한 입력 받은 출석 시간 전에 도착한 경우 출석 상태 가져오기") + void 입력_받은_출석_시간으로_출석_상태_가져오기() { + LocalDate today = LocalDate.of(2024, 12, 13); + LocalTime input = LocalTime.parse("09:03"); + + AttendanceStatus expect = AttendanceStatus.ATTENDANCE; + AttendanceStatus result = AttendanceStatus.calculateAttendanceStatus(today, input); + Assertions.assertEquals(expect, result); + } + + @Test + @DisplayName("월요일을 제외한 입력 받은 출석 시간 5분 이후 도착한 경우 지각 상태 가져오기") + void 입력_받은_지각_시간으로_지각_상태_가져오기() { + LocalDate today = LocalDate.of(2024, 12, 13); + LocalTime input = LocalTime.parse("10:06"); + + AttendanceStatus expect = AttendanceStatus.LATE; + AttendanceStatus result = AttendanceStatus.calculateAttendanceStatus(today, input); + Assertions.assertEquals(expect, result); + } + + @Test + @DisplayName("월요일을 제외한 입력 받은 출석 시간을 30분 이후 도착한 경우 결석 상태 가져오기") + void 입력_받은_결석_시간으로_결석_상태_가져오기() { + LocalDate today = LocalDate.of(2024, 12, 13); + LocalTime input = LocalTime.parse("10:31"); + AttendanceStatus expect = AttendanceStatus.ABSENT; + AttendanceStatus result = AttendanceStatus.calculateAttendanceStatus(today, input); + Assertions.assertEquals(expect, result); + } + + @Test + @DisplayName("월요일인 경우 입력 받은 출석 시간으로 출석 상태 가져오기") + void 월요일의_경우_입력_받은_출석_시간으로_상태_가져오기() { + LocalDate today = LocalDate.of(2024, 12, 9); + LocalTime input = LocalTime.parse("13:00"); + + AttendanceStatus expect = AttendanceStatus.ATTENDANCE; + AttendanceStatus result = AttendanceStatus.calculateAttendanceStatus(today, input); + Assertions.assertEquals(expect, result); + } + + @Test + @DisplayName("출석 횟수 계산하기") + void 출석_횟수_계산_테스트(){ + Map testMap = Map.of( + LocalDate.of(2024,12,13), LocalTime.of(10,0), + LocalDate.of(2024,12,12), LocalTime.of(10,0), + LocalDate.of(2024,12,11), LocalTime.of(10,6), + LocalDate.of(2024,12,10), LocalTime.of(10,0), + LocalDate.of(2024,12,9), LocalTime.of(13,0)); + long expect = 4; + long result = AttendanceStatus.calculateAttendanceStatusCount(testMap, AttendanceStatus.ATTENDANCE); + Assertions.assertEquals(expect, result); + } + + @Test + @DisplayName("지각 횟수 계산하기") + void 지각_횟수_계산_테스트(){ + Map testMap = Map.of( + LocalDate.of(2024,12,13), LocalTime.of(10,0), + LocalDate.of(2024,12,12), LocalTime.of(10,0), + LocalDate.of(2024,12,11), LocalTime.of(10,6), + LocalDate.of(2024,12,10), LocalTime.of(10,0), + LocalDate.of(2024,12,9), LocalTime.of(13,0)); + long expect = 1; + long result = AttendanceStatus.calculateAttendanceStatusCount(testMap, AttendanceStatus.LATE); + Assertions.assertEquals(expect, result); + } + + @Test + @DisplayName("결설 횟수 계산하기") + void 결석_횟수_계산_테스트(){ + Map testMap = Map.of( + LocalDate.of(2024,12,13), LocalTime.of(10,31), + LocalDate.of(2024,12,12), LocalTime.of(10,50), + LocalDate.of(2024,12,11), LocalTime.of(10,6), + LocalDate.of(2024,12,10), LocalTime.of(10,0), + LocalDate.of(2024,12,9), LocalTime.of(13,0)); + long expect = 2; + long result = AttendanceStatus.calculateAttendanceStatusCount(testMap, AttendanceStatus.ABSENT); + Assertions.assertEquals(expect, result); + } +} diff --git a/src/test/java/model/AttendanceTimeRecordTest.java b/src/test/java/model/AttendanceTimeRecordTest.java new file mode 100644 index 000000000..48d71b30b --- /dev/null +++ b/src/test/java/model/AttendanceTimeRecordTest.java @@ -0,0 +1,35 @@ +package model; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AttendanceTimeRecordTest { + AttendanceTimeRecord attendanceTimeRecord = new AttendanceTimeRecord(List.of( + LocalDateTime.of(2024, 12, 13, 10, 0), + LocalDateTime.of(2024, 12, 12, 10, 6), + LocalDateTime.of(2024, 12, 11, 10, 31), + LocalDateTime.of(2024, 12, 10, 10, 0), + LocalDateTime.of(2024, 12, 9, 13, 0) + )); + + @Test + @DisplayName("테스트 날짜가 저장되어있지 않는지 테스트") + void 테스트_날짜가_존재하지_않는지_테스트() { + LocalDate testDate = LocalDate.of(2024,12,20); + boolean result = attendanceTimeRecord.checkAttendanceRecordByLocalDate(testDate); + assertFalse(result); + } + + @Test + @DisplayName("테스트 날짜가 존재하는지 테스트") + void 테스트_날짜가_존재하는지_테스트() { + LocalDate testDate = LocalDate.of(2024,12,13); + boolean result = attendanceTimeRecord.checkAttendanceRecordByLocalDate(testDate); + assertTrue(result); + } +} \ No newline at end of file diff --git a/src/test/java/model/CampusOperatingHoursTest.java b/src/test/java/model/CampusOperatingHoursTest.java new file mode 100644 index 000000000..6116b3553 --- /dev/null +++ b/src/test/java/model/CampusOperatingHoursTest.java @@ -0,0 +1,30 @@ +package model; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CampusOperatingHoursTest { + + @Test + @DisplayName("캠퍼스 운영 시간 전 예외 테스트") + void 캠퍼스_운영_시간_전_예외_테스트() { + LocalTime testTime = LocalTime.of(7,59); + assertThrows( + IllegalArgumentException.class, + ()->CampusOperatingHours.validateOperatingHours(testTime) + ); + } + + @Test + @DisplayName("캠퍼스 운영 시간 종료 후 예외 테스트") + void 캠퍼스_운영_시간_종료_후_예외_테스트() { + LocalTime testTime = LocalTime.of(23,1); + assertThrows( + IllegalArgumentException.class, + ()->CampusOperatingHours.validateOperatingHours(testTime) + ); + } +} \ No newline at end of file diff --git a/src/test/java/model/HolidayTest.java b/src/test/java/model/HolidayTest.java new file mode 100644 index 000000000..54adcab19 --- /dev/null +++ b/src/test/java/model/HolidayTest.java @@ -0,0 +1,45 @@ +package model; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDate; +import java.time.format.TextStyle; +import java.util.Locale; +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; + +class HolidayTest { + + @ParameterizedTest + @CsvSource({ + "2024-12-14, true", + "2024-12-15, true", + "2024-12-25, true" + }) + @DisplayName("휴일에 대한 출석 여부 확인") + void 출석_여부_확인(LocalDate localDate, boolean expected) { + boolean result = Holiday.checkHoliday(localDate); + assertEquals(expected, result); + } + + @ParameterizedTest + @CsvSource({ + "2024-12-14", + "2024-12-15", + "2024-12-25" + }) + @DisplayName("휴일에 대한 출석 시간 예외 처리") + void 휴일_출석_예외처리(LocalDate localDate) { + assertThatThrownBy(() -> Holiday.validateHoliday(localDate)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] " + +localDate.getMonthValue() + "월 " + +localDate.getDayOfMonth()+"일 " + +localDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.KOREA) + + "은 등교일이 아닙니다."); + } + +} \ No newline at end of file diff --git a/src/test/java/model/StudentTest.java b/src/test/java/model/StudentTest.java index 42327b929..d17ea85dd 100644 --- a/src/test/java/model/StudentTest.java +++ b/src/test/java/model/StudentTest.java @@ -1,70 +1,133 @@ package model; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; -import controller.Controller; import java.time.LocalDate; import java.time.LocalDateTime; -import org.junit.jupiter.api.BeforeEach; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class StudentTest { - private final AttendanceBook studentRepository = new Controller().createStudentRepository(); - Student student = studentRepository.findStudentByName("빙티"); +public class StudentTest { - @BeforeEach - void studentRepositoryUpdate(){ - student.getAttendanceRecords().updateStateNotExistInFile(LocalDate.of(2024,12,13)); + private final Student student = new Student("빙티", Arrays + .asList(LocalDateTime.of(2024, 12, 2, 13, 0), + LocalDateTime.of(2024, 12, 3, 10, 7))); + + @Test + @DisplayName("출석 등록 시간 확인 테스트") + void 출석_등록_시간_확인_테스트() { + LocalDate todayDate = LocalDate.of(2024, 12, 13); + LocalTime attendanceTime = LocalTime.parse("10:59"); + + LocalTime expect = LocalTime.of(10, 59); + + student.registerAttendanceRecord(todayDate, attendanceTime); + LocalTime result = student.findAttendanceLocalTimeByLocalDate(todayDate); + assertEquals(expect, result); } @Test - @DisplayName("출결 등록 테스트") - void attendanceRegister() { - LocalDateTime localDateTime = LocalDateTime.of(2024,12,16,13,6); - student.attendanceRegister(localDateTime); - AttendanceStatus expect = AttendanceStatus.LATE; - AttendanceStatus result = student.getAttendanceRecords().findAttendanceStatusByLocalDateTime(localDateTime); + @DisplayName("출석 정보 수정 후 수정 시간 확인 테스트") + void 출석_정보_수정_후_수정_시간_확인_테스트() { + //given + LocalDate recordData = LocalDate.of(2024, 12, 13); + LocalTime attendanceTime = LocalTime.parse("10:59"); + + int modifyDate = 13; + LocalTime modifyTime = LocalTime.parse("10:00"); + + LocalTime expect = LocalTime.of(10, 0); + student.registerAttendanceRecord(recordData, attendanceTime); + //when + student.modifyAttendanceRecord(modifyDate, modifyTime); + //then + LocalTime result = student.findAttendanceLocalTimeByLocalDate(recordData); assertEquals(expect, result); } @Test - @DisplayName("학생 출결 기록 수정 테스트") - void modifyAttendanceRecord() { - LocalDateTime attendanceTime = LocalDateTime.of(2024,12,16,12,59); - student.attendanceRegister(attendanceTime); - AttendanceStatus expect = AttendanceStatus.ABSENT; + @DisplayName("학생의 지각 횟수 확인 테스트") + void 학생의_지각_횟수_확인_테스트() { + long expect = 1; + + long result = student.calculateLateCount(); - LocalDateTime absentTime = LocalDateTime.of(2024,12,16,13,31); - student.modifyAttendanceRecord(absentTime); - AttendanceStatus result = student.getAttendanceRecords().findAttendanceStatusByLocalDateTime(attendanceTime); assertEquals(expect, result); } @Test - void findStateByLocalDateTime() { - LocalDateTime attendanceTime = LocalDateTime.of(2024,12,16,12,59); - student.attendanceRegister(attendanceTime); - String expect = "출석"; - String result = student.findStateByLocalDateTime(attendanceTime); + @DisplayName("오늘을 기준으로 출석 기록이 없다면 결석 처리한다.") + void 오늘을_기준으로_출석_기록이_없다면_결석_처리() { + LocalDate today = LocalDate.of(2024, 12, 13); + AttendanceStatus expect = AttendanceStatus.ABSENT; + student.updateNonAttendanceRecordStatusIsAbsent(today); + LocalTime localTime = student.findAttendanceLocalTimeByLocalDate(LocalDate.of(2024,12,12)); + AttendanceStatus result = AttendanceStatus.calculateAttendanceStatus(today, localTime); assertEquals(expect, result); } @Test - @DisplayName("지각 3회당 결석 1회 추가 테스트") - void calculateAbsent(){ - student.attendanceRegister( - LocalDateTime.of(2024,12,16,13,6)); - student.attendanceRegister( - LocalDateTime.of(2024,12,17,10,6)); - student.attendanceRegister( - LocalDateTime.of(2024,12,18,10,6)); - long expect = 11; - long result = student.calculateAbsent(); - - long lateCount = student.getAttendanceCount().getLateTotalCount(); - System.out.println(lateCount); + @DisplayName("이미 출석 기록이 있을 경우 출석 시도 시 예외 테스트") + void 이미_출석_기록이_존재하는_경우() { + LocalDate todayDate = LocalDate.of(2024, 12, 13); + LocalTime attendanceTime = LocalTime.parse("10:59"); + LocalTime duplicateAttendanceTime = LocalTime.parse("11:02"); + + student.registerAttendanceRecord(todayDate, attendanceTime); + assertThatThrownBy(() -> student.registerAttendanceRecord(todayDate, duplicateAttendanceTime)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 출석기록이 존재합니다."); + } + + @Test + @DisplayName("지각 횟수 가져오기 테스트") + void 지각_횟수_확인_테스트(){ + List testLocalDateTime = List.of( + LocalDateTime.of(2024,12,13,10,7), + LocalDateTime.of(2024,12,12,10,6), + LocalDateTime.of(2024,12,11,10,0), + LocalDateTime.of(2024,12,10,10,8) + ); + Student testStudent = new Student("테스트", testLocalDateTime); + long expect = 3; + long result = testStudent.calculateLateCount(); + assertEquals(expect, result); + } + + @Test + @DisplayName("순수 결석 횟수 가져오기 테스트") + void 순수_결석_횟수_확인_테스트(){ + List testLocalDateTime = List.of( + LocalDateTime.of(2024,12,13,10,31), + LocalDateTime.of(2024,12,12,10,6), + LocalDateTime.of(2024,12,11,10,31), + LocalDateTime.of(2024,12,10,10,8) + ); + Student testStudent = new Student("테스트", testLocalDateTime); + long expect = 2; + long result = testStudent.calculateAbsentCount(); assertEquals(expect, result); } -} \ No newline at end of file + + @Test + @DisplayName("지각 3회는 결석1회로 전환해서 결석 횟수 가져오기 테스트") + void 지각_3회_결석_1회로_전환_후_결석_횟수_확인_테스트(){ + List testLocalDateTime = List.of( + LocalDateTime.of(2024,12,13,10,31), + LocalDateTime.of(2024,12,12,10,6), + LocalDateTime.of(2024,12,11,10,9), + LocalDateTime.of(2024,12,10,10,8), + LocalDateTime.of(2024,12,9,10,10) + ); + Student testStudent = new Student("테스트", testLocalDateTime); + long expect = 2; + long result = testStudent.calculateTotalAbsentCount(); + assertEquals(expect, result); + } + +} diff --git a/src/test/java/model/WeeklyAttendanceScheduleTest.java b/src/test/java/model/WeeklyAttendanceScheduleTest.java new file mode 100644 index 000000000..5b90970ff --- /dev/null +++ b/src/test/java/model/WeeklyAttendanceScheduleTest.java @@ -0,0 +1,48 @@ +package model; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.TextStyle; +import java.util.Locale; +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; + +class WeeklyAttendanceScheduleTest { + + @ParameterizedTest + @CsvSource({ + "2024-12-09, 13:00", + "2024-12-10, 10:00", + "2024-12-11, 10:00", + "2024-12-12, 10:00", + "2024-12-13, 10:00" + }) + @DisplayName("주중 출석 시작 시간 확인") + void 출석_시작_시간_찾기(LocalDate localDate, String expectedTime) { + LocalTime expect = LocalTime.parse(expectedTime); + LocalTime result = WeeklyAttendanceSchedule.findAttendanceScheduleByLocalDate(localDate); + assertEquals(expect, result); + } + + @ParameterizedTest + @CsvSource({ + "2024-12-14", + "2024-12-15", + "2024-12-25" + }) + @DisplayName("휴일에 대한 출석 시간 예외 처리") + void 휴일_출석_예외처리(LocalDate localDate) { + assertThatThrownBy(() -> WeeklyAttendanceSchedule.findAttendanceScheduleByLocalDate(localDate)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] " + +localDate.getMonthValue() + "월 " + +localDate.getDayOfMonth()+"일 " + +localDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.KOREA) + + "은 등교일이 아닙니다."); + } +} diff --git a/src/test/java/util/FileInformationProviderTest.java b/src/test/java/util/FileInformationProviderTest.java new file mode 100644 index 000000000..98efd03f9 --- /dev/null +++ b/src/test/java/util/FileInformationProviderTest.java @@ -0,0 +1,34 @@ +package util; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FileInformationProviderTest { + @Test + @DisplayName("출석부 기록 전환 후 학생 수 확인 테스트") + void 출석부_기록_전환_후_학생_수_확인_테스트() throws IOException { + FileInformationProvider fileProvider = new FileInformationProvider(); + Map> studentRecord = fileProvider.loadStudentAttendance(); + int expect = 5; + int result = studentRecord.size(); + assertEquals(expect, result); + } + + @Test + @DisplayName("출석부 기록 전환 후 학생 기록 확인 테스트") + void 출석부_기록_전환_후_학생_기록_확인_테스트() throws IOException { + FileInformationProvider fileProvider = new FileInformationProvider(); + Map> studentRecord = fileProvider.loadStudentAttendance(); + List attendanceRecord = studentRecord.get("빙티"); + int expect = 2; + int result = attendanceRecord.size(); + assertEquals(expect, result); + } + +} \ No newline at end of file