Merge branch 'develop' into feature/ui
This commit is contained in:
+12
@@ -42,12 +42,24 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.14.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.lanterna</groupId>
|
||||
<artifactId>lanterna</artifactId>
|
||||
<version>3.1.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.14.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package ru.project.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
import ru.project.student.Student;
|
||||
import ru.project.student.StudentBuilder;
|
||||
public class StudentCsvReader implements Iterator<Student>, AutoCloseable {
|
||||
private static final String GROUP_NUMBER = "groupNumber";
|
||||
private static final String AVERAGE_GRADE = "averageGrade";
|
||||
private static final String RECORD_BOOK_NUMBER = "recordBookNumber";
|
||||
private final CSVParser parser;
|
||||
private final Iterator<CSVRecord> records;
|
||||
private int lineOfFile=0;
|
||||
public StudentCsvReader(Path path) {
|
||||
if (!path.isAbsolute()) {
|
||||
throw new IllegalArgumentException("File path must be absolute");
|
||||
}
|
||||
try {
|
||||
CSVFormat format = CSVFormat.DEFAULT.builder()
|
||||
.setHeader()
|
||||
.setSkipHeaderRecord(true)
|
||||
.get();
|
||||
this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format);
|
||||
validateHeader(parser.getHeaderMap());
|
||||
this.records = parser.iterator();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return records.hasNext();
|
||||
}
|
||||
@Override
|
||||
public Student next() {
|
||||
if (!records.hasNext()) {
|
||||
throw new NoSuchElementException("No more records at line " + (lineOfFile + 1));
|
||||
}
|
||||
lineOfFile++;
|
||||
CSVRecord record = records.next();
|
||||
if (record.size() != 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"Problem at line " + lineOfFile + ". Expected 3 columns, but got " + record.size());
|
||||
}
|
||||
String groupNumber = record.get(GROUP_NUMBER);
|
||||
String averageGradeStr = record.get(AVERAGE_GRADE);
|
||||
String recordBookNumberStr = record.get(RECORD_BOOK_NUMBER);
|
||||
double averageGrade;
|
||||
int recordBookNumber;
|
||||
try {
|
||||
averageGrade = Double.parseDouble(averageGradeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Problem at line " + lineOfFile + ": cannot parse averageGrade '" + averageGradeStr + "' as double", e);
|
||||
}
|
||||
try {
|
||||
recordBookNumber = Integer.parseInt(recordBookNumberStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Problem at line " + lineOfFile + ": cannot parse recordBookNumber '" + recordBookNumberStr + "' as int", e);
|
||||
}
|
||||
return new StudentBuilder()
|
||||
.setGroupNumber(groupNumber)
|
||||
.setAverageGrade(averageGrade)
|
||||
.setRecordBookNumber(recordBookNumber)
|
||||
.build();
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
parser.close();
|
||||
}
|
||||
private static void validateHeader(Map<String, Integer> headerMap) {
|
||||
String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER};
|
||||
if (headerMap.size() != expected.length
|
||||
|| !headerMap.keySet().containsAll(Arrays.asList(expected))) {
|
||||
throw new IllegalArgumentException("Invalid CSV header");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package ru.project.io;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class StudentCsvWriter {
|
||||
|
||||
private static final String GROUP_NUMBER_HEADER = "groupNumber";
|
||||
private static final String AVERAGE_GRADE_HEADER = "averageGrade";
|
||||
private static final String RECORD_BOOK_NUMBER_HEADER = "recordBookNumber";
|
||||
|
||||
private final String absoluteFilePath;
|
||||
|
||||
public StudentCsvWriter(String absoluteFilePath) {
|
||||
this.absoluteFilePath = absoluteFilePath;
|
||||
}
|
||||
|
||||
public void write(Student student) throws IOException {
|
||||
ensureFileExists();
|
||||
|
||||
boolean fileIsEmpty = isFileEmpty();
|
||||
|
||||
try (CSVPrinter printer = createPrinter()) {
|
||||
if (fileIsEmpty) {
|
||||
writeHeader(printer);
|
||||
}
|
||||
|
||||
writeStudent(printer, student);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(MyList<Student>[] students) throws IOException {
|
||||
ensureFileExists();
|
||||
|
||||
boolean fileIsEmpty = isFileEmpty();
|
||||
|
||||
try (CSVPrinter printer = createPrinter()) {
|
||||
if (fileIsEmpty) {
|
||||
writeHeader(printer);
|
||||
}
|
||||
|
||||
for (MyList<Student> studentList : students) {
|
||||
for (int i = 0; i < studentList.size(); i++) {
|
||||
writeStudent(printer, studentList.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CSVPrinter createPrinter() throws IOException {
|
||||
BufferedWriter writer =
|
||||
new BufferedWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(absoluteFilePath, true), StandardCharsets.UTF_8));
|
||||
|
||||
return new CSVPrinter(writer, CSVFormat.DEFAULT);
|
||||
}
|
||||
|
||||
private void writeHeader(CSVPrinter printer) throws IOException {
|
||||
printer.printRecord(GROUP_NUMBER_HEADER, AVERAGE_GRADE_HEADER, RECORD_BOOK_NUMBER_HEADER);
|
||||
}
|
||||
|
||||
private void writeStudent(CSVPrinter printer, Student student) throws IOException {
|
||||
|
||||
printer.printRecord(
|
||||
student.getGroupNumber(), student.getAverageGrade(), student.getRecordBookNumber());
|
||||
}
|
||||
|
||||
private void ensureFileExists() throws IOException {
|
||||
File file = new File(absoluteFilePath);
|
||||
|
||||
if (!file.isFile()) {
|
||||
throw new IOException("CSV file does not exist: " + absoluteFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFileEmpty() {
|
||||
return new File(absoluteFilePath).length() == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package ru.project.random;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
import ru.project.student.StudentBuilder;
|
||||
|
||||
public class RandomStudentGenerator {
|
||||
private static final Random RANDOM = new Random();
|
||||
private String minGroupNumber;
|
||||
private String maxGroupNumber;
|
||||
private Double minAverageGrade;
|
||||
private Double maxAverageGrade;
|
||||
private Integer minRecordBookNumber;
|
||||
private Integer maxRecordBookNumber;
|
||||
private static final double DEFAULT_MIN_GRADE = 0.0;
|
||||
private static final double DEFAULT_MAX_GRADE = 5.0;
|
||||
private static final int DEFAULT_MIN_RECORD_BOOK = 1;
|
||||
private static final String GROUP_PATTERN_REGEX = "^[A-Z][0-9]{2}$";
|
||||
private static final Pattern GROUP_PATTERN = Pattern.compile(GROUP_PATTERN_REGEX);
|
||||
|
||||
public RandomStudentGenerator setMinGroupNumber(String minGroupNumber) {
|
||||
if (minGroupNumber != null && !GROUP_PATTERN.matcher(minGroupNumber).matches()) {
|
||||
throw new IllegalArgumentException(
|
||||
"minGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX);
|
||||
}
|
||||
this.minGroupNumber = minGroupNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMaxGroupNumber(String maxGroupNumber) {
|
||||
if (maxGroupNumber != null && !GROUP_PATTERN.matcher(maxGroupNumber).matches()) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX);
|
||||
}
|
||||
this.maxGroupNumber = maxGroupNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMinAverageGrade(double minAverageGrade) {
|
||||
if (minAverageGrade < DEFAULT_MIN_GRADE || minAverageGrade > DEFAULT_MAX_GRADE) {
|
||||
throw new IllegalArgumentException(
|
||||
"minAverageGrade out of Student allowed range ["
|
||||
+ DEFAULT_MIN_GRADE
|
||||
+ ", "
|
||||
+ DEFAULT_MAX_GRADE
|
||||
+ "]");
|
||||
}
|
||||
this.minAverageGrade = minAverageGrade;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMaxAverageGrade(double maxAverageGrade) {
|
||||
if (maxAverageGrade < DEFAULT_MIN_GRADE || maxAverageGrade > DEFAULT_MAX_GRADE) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxAverageGrade out of Student allowed range ["
|
||||
+ DEFAULT_MIN_GRADE
|
||||
+ ", "
|
||||
+ DEFAULT_MAX_GRADE
|
||||
+ "]");
|
||||
}
|
||||
this.maxAverageGrade = maxAverageGrade;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMinRecordBookNumber(int minRecordBookNumber) {
|
||||
if (minRecordBookNumber < DEFAULT_MIN_RECORD_BOOK) {
|
||||
throw new IllegalArgumentException(
|
||||
"minRecordBookNumber must be >= " + DEFAULT_MIN_RECORD_BOOK);
|
||||
}
|
||||
this.minRecordBookNumber = minRecordBookNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMaxRecordBookNumber(int maxRecordBookNumber) {
|
||||
if (maxRecordBookNumber < DEFAULT_MIN_RECORD_BOOK) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxRecordBookNumber must be >= " + DEFAULT_MIN_RECORD_BOOK);
|
||||
}
|
||||
this.maxRecordBookNumber = maxRecordBookNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MyList<Student> generate(int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("size must be non-negative");
|
||||
}
|
||||
validateRanges();
|
||||
MyList<Student> result = new MyLinkedList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
String group = generateGroupNumber();
|
||||
double grade = generateAverageGrade();
|
||||
int recordBook = generateRecordBookNumber();
|
||||
|
||||
Student student =
|
||||
new StudentBuilder()
|
||||
.setGroupNumber(group)
|
||||
.setAverageGrade(grade)
|
||||
.setRecordBookNumber(recordBook)
|
||||
.build();
|
||||
|
||||
result.add(student);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void validateRanges() {
|
||||
|
||||
double effectiveMinGrade = (minAverageGrade != null) ? minAverageGrade : DEFAULT_MIN_GRADE;
|
||||
double effectiveMaxGrade = (maxAverageGrade != null) ? maxAverageGrade : DEFAULT_MAX_GRADE;
|
||||
if (effectiveMinGrade > effectiveMaxGrade) {
|
||||
throw new IllegalArgumentException("minAverageGrade cannot be greater than maxAverageGrade");
|
||||
}
|
||||
int effectiveMinRecord =
|
||||
(minRecordBookNumber != null) ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK;
|
||||
int effectiveMaxRecord =
|
||||
(maxRecordBookNumber != null) ? maxRecordBookNumber : Integer.MAX_VALUE;
|
||||
if (effectiveMaxRecord == Integer.MAX_VALUE) {
|
||||
effectiveMaxRecord = effectiveMinRecord + 999_999;
|
||||
}
|
||||
if (effectiveMinRecord > effectiveMaxRecord) {
|
||||
throw new IllegalArgumentException(
|
||||
"minRecordBookNumber cannot be greater than maxRecordBookNumber");
|
||||
}
|
||||
if (minGroupNumber != null && maxGroupNumber != null) {
|
||||
if (minGroupNumber.compareTo(maxGroupNumber) > 0) {
|
||||
throw new IllegalArgumentException("minGroupNumber cannot be greater than maxGroupNumber");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String generateGroupNumber() {
|
||||
String min = (minGroupNumber != null) ? minGroupNumber : "A00";
|
||||
String max = (maxGroupNumber != null) ? maxGroupNumber : "Z99";
|
||||
char minLetter = min.charAt(0);
|
||||
char maxLetter = max.charAt(0);
|
||||
int minDigit = Integer.parseInt(min.substring(1));
|
||||
int maxDigit = Integer.parseInt(max.substring(1));
|
||||
char letter = (char) (minLetter + RANDOM.nextInt((maxLetter - minLetter) + 1));
|
||||
int digit;
|
||||
if (letter == minLetter && letter == maxLetter) {
|
||||
digit = minDigit + RANDOM.nextInt((maxDigit - minDigit) + 1);
|
||||
} else if (letter == minLetter) {
|
||||
digit = minDigit + RANDOM.nextInt(100 - minDigit);
|
||||
} else if (letter == maxLetter) {
|
||||
digit = RANDOM.nextInt(maxDigit + 1);
|
||||
} else {
|
||||
digit = RANDOM.nextInt(100);
|
||||
}
|
||||
return letter + String.format("%02d", digit);
|
||||
}
|
||||
|
||||
private double generateAverageGrade() {
|
||||
double min = (minAverageGrade != null) ? minAverageGrade : DEFAULT_MIN_GRADE;
|
||||
double max = (maxAverageGrade != null) ? maxAverageGrade : DEFAULT_MAX_GRADE;
|
||||
|
||||
double range = max - min;
|
||||
return min + (range * RANDOM.nextDouble());
|
||||
}
|
||||
|
||||
private int generateRecordBookNumber() {
|
||||
int min = (minRecordBookNumber != null) ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK;
|
||||
int max =
|
||||
(maxRecordBookNumber != null) ? maxRecordBookNumber : min + 999_999; // разумный максимум
|
||||
if (max < min) {
|
||||
max = min;
|
||||
}
|
||||
return min + RANDOM.nextInt((max - min) + 1);
|
||||
}
|
||||
}
|
||||
@@ -1,138 +1,137 @@
|
||||
package ru.project.finder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
public class StudentOccurrenceIndexFinderTest {
|
||||
private StudentOccurrenceIndexFinder finder;
|
||||
private MyList<Student> students;
|
||||
|
||||
public class StudentOccurrenceIndexFinderTest
|
||||
{
|
||||
private StudentOccurrenceIndexFinder finder;
|
||||
private MyList<Student> students;
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
finder = new StudentOccurrenceIndexFinder();
|
||||
students = new MyLinkedList<>();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
finder = new StudentOccurrenceIndexFinder();
|
||||
students = new MyLinkedList<>();
|
||||
}
|
||||
@Test
|
||||
void shouldTesfindOccurrences_emptyList_returnsEmptyArray() {
|
||||
int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1));
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_emptyList_returnsEmptyArray() {
|
||||
int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1));
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
@Test
|
||||
void shouldTesfindOccurrences_noMatches_returnsEmptyArray() {
|
||||
students.add(new Student("A12", 4.0, 101));
|
||||
students.add(new Student("B23", 3.5, 102));
|
||||
Student target = new Student("C34", 4.8, 103); // такого нет
|
||||
@Test
|
||||
void shouldTesfindOccurrences_noMatches_returnsEmptyArray() {
|
||||
students.add(new Student("A12", 4.0, 101));
|
||||
students.add(new Student("B23", 3.5, 102));
|
||||
Student target = new Student("C34", 4.8, 103); // такого нет
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
@Test
|
||||
void shouldTesfindOccurrences_singleMatch_returnsCorrectIndex() {
|
||||
students.add(new Student("A12", 4.0, 101));
|
||||
students.add(new Student("B23", 3.5, 102)); // target здесь
|
||||
students.add(new Student("C34", 4.8, 103));
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
Student target = new Student("B23", 3.5, 102);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
@Test
|
||||
void shouldTesfindOccurrences_singleMatch_returnsCorrectIndex() {
|
||||
students.add(new Student("A12", 4.0, 101));
|
||||
students.add(new Student("B23", 3.5, 102)); // target здесь
|
||||
students.add(new Student("C34", 4.8, 103));
|
||||
|
||||
assertEquals(1, result.length);
|
||||
assertEquals(1, result[0]);
|
||||
}
|
||||
@Test
|
||||
void shouldTesfindOccurrences_multipleMatches_returnsAllIndicesSorted() {
|
||||
// индексы: 0, 2, 4 — должны вернуться в порядке возрастания
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
students.add(new Student("B23", 3.8, 102));
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
students.add(new Student("C34", 4.0, 103));
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
Student target = new Student("B23", 3.5, 102);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(1, result.length);
|
||||
assertEquals(1, result[0]);
|
||||
}
|
||||
|
||||
assertEquals(3, result.length);
|
||||
assertArrayEquals(new int[]{0, 2, 4}, result);
|
||||
}
|
||||
@Test
|
||||
void shouldTesfindOccurrences_multipleMatches_returnsAllIndicesSorted() {
|
||||
// индексы: 0, 2, 4 — должны вернуться в порядке возрастания
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
students.add(new Student("B23", 3.8, 102));
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
students.add(new Student("C34", 4.0, 103));
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() {
|
||||
Student s = new Student("X99", 5.0, 999);
|
||||
students.add(s);
|
||||
students.add(s);
|
||||
students.add(s);
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
|
||||
int[] result = finder.findOccurrences(students, s);
|
||||
assertEquals(3, result.length);
|
||||
assertArrayEquals(new int[]{0, 1, 2}, result);
|
||||
}
|
||||
assertEquals(3, result.length);
|
||||
assertArrayEquals(new int[] {0, 2, 4}, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentAverageGrade_noMatch() {
|
||||
students.add(new Student("A12", 4.50, 101));
|
||||
Student target = new Student("A12", 4.51, 101); // отличается балл
|
||||
@Test
|
||||
void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() {
|
||||
Student s = new Student("X99", 5.0, 999);
|
||||
students.add(s);
|
||||
students.add(s);
|
||||
students.add(s);
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
int[] result = finder.findOccurrences(students, s);
|
||||
assertEquals(3, result.length);
|
||||
assertArrayEquals(new int[] {0, 1, 2}, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentAverageGrade_noMatch() {
|
||||
students.add(new Student("A12", 4.50, 101));
|
||||
Student target = new Student("A12", 4.51, 101); // отличается балл
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentGroupNumber_noMatch() {
|
||||
students.add(new Student("A12", 4.5, 101));
|
||||
Student target = new Student("B12", 4.5, 101); // другая группа
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentRecordBookNumber_noMatch() {
|
||||
students.add(new Student("A12", 4.5, 101));
|
||||
Student target = new Student("A12", 4.5, 102); // другой номер зачетки
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_nullList_throwsException() {
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(null, target));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_nullTarget_throwsException() {
|
||||
assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(students, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_resultIsSorted() {
|
||||
String[] validGroups = {"A00", "A01", "B12", "C99", "X55"};
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
String group = validGroups[i % validGroups.length];
|
||||
double grade = (3.0 + (i % 3)) / 2.0;
|
||||
if (i % 3 == 0) {
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentGroupNumber_noMatch() {
|
||||
students.add(new Student("A12", 4.5, 101));
|
||||
Student target = new Student("B12", 4.5, 101); // другая группа
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
} else {
|
||||
students.add(new Student(group, grade, 200 + i));
|
||||
}
|
||||
}
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentRecordBookNumber_noMatch() {
|
||||
students.add(new Student("A12", 4.5, 101));
|
||||
Student target = new Student("A12", 4.5, 102); // другой номер зачетки
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_nullList_throwsException() {
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(null, target));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_nullTarget_throwsException() {
|
||||
assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(students, null));
|
||||
}
|
||||
@Test
|
||||
void shouldTesfindOccurrences_resultIsSorted() {
|
||||
String[] validGroups = {"A00", "A01", "B12", "C99", "X55"};
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
String group = validGroups[i % validGroups.length];
|
||||
double grade = (3.0 + (i % 3)) / 2.0;
|
||||
if (i % 3 == 0) {
|
||||
|
||||
students.add(new Student("A12", 4.5, 101));
|
||||
} else {
|
||||
students.add(new Student(group, grade, 200 + i));
|
||||
}
|
||||
}
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
|
||||
|
||||
for (int i = 1; i < result.length; i++) {
|
||||
assertTrue(result[i] > result[i - 1], "Результат должен быть отсортирован");
|
||||
}
|
||||
for (int i = 1; i < result.length; i++) {
|
||||
assertTrue(result[i] > result[i - 1], "Результат должен быть отсортирован");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package ru.project.io;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.StreamSupport;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import ru.project.student.Student;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.collectors.MyCollectors;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
class StudentCsvReaderTest {
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
@Test
|
||||
void shouldTestreadValidCsvIntoMyList() throws IOException {
|
||||
Path csv = tempDir.resolve("students.csv");
|
||||
String content = String.join("\n",
|
||||
"groupNumber,averageGrade,recordBookNumber",
|
||||
"M10,4.5,12345",
|
||||
"B05,3.8,12346"
|
||||
);
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
|
||||
var studentStream = StreamSupport.stream(
|
||||
Spliterators.spliteratorUnknownSize(reader, 0),
|
||||
false
|
||||
);
|
||||
MyList<Student> students = studentStream
|
||||
.collect(MyCollectors.toMyList());
|
||||
assertEquals(2, students.size());
|
||||
assertEquals("M10", students.get(0).getGroupNumber());
|
||||
assertEquals(4.5, students.get(0).getAverageGrade(), 0.0);
|
||||
assertEquals(12345, students.get(0).getRecordBookNumber());
|
||||
assertEquals("B05", students.get(1).getGroupNumber());
|
||||
assertEquals(3.8, students.get(1).getAverageGrade(), 0.0);
|
||||
assertEquals(12346, students.get(1).getRecordBookNumber());
|
||||
}
|
||||
}
|
||||
@Test
|
||||
void shouldTestinvalidHeaderThrowsException() throws IOException {
|
||||
Path csv = tempDir.resolve("bad-header.csv");
|
||||
String content = """
|
||||
badGroup,badGrade,badBook
|
||||
M101,4.5,12345
|
||||
""";
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
IllegalArgumentException ex = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> new StudentCsvReader(csv.toAbsolutePath())
|
||||
);
|
||||
assertTrue(ex.getMessage().contains("Invalid CSV header"));
|
||||
}
|
||||
@Test
|
||||
void shouldTestwrongColumnCountThrowsException() throws IOException {
|
||||
Path csv = tempDir.resolve("wrong-columns.csv");
|
||||
String content = """
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
M101,4.5
|
||||
""";
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
|
||||
assertTrue(reader.hasNext());
|
||||
|
||||
IllegalArgumentException ex = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
reader::next
|
||||
);
|
||||
assertTrue(ex.getMessage().contains("Expected 3 columns"));
|
||||
}
|
||||
}
|
||||
@Test
|
||||
void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException {
|
||||
Path csv = tempDir.resolve("parse-error.csv");
|
||||
String content = """
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
M101,not-a-number,12345
|
||||
""";
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
|
||||
assertTrue(reader.hasNext());
|
||||
|
||||
IllegalArgumentException ex = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
reader::next
|
||||
);
|
||||
assertTrue(ex.getMessage().contains("cannot parse averageGrade"));
|
||||
}
|
||||
}
|
||||
@Test
|
||||
void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException {
|
||||
Path csv = tempDir.resolve("empty-data.csv");
|
||||
String content = """
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
""";
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
|
||||
// ИСПРАВЛЕНО: добавлен try-with-resources
|
||||
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
|
||||
assertFalse(reader.hasNext());
|
||||
|
||||
assertThrows(NoSuchElementException.class, reader::next);
|
||||
}
|
||||
}
|
||||
@Test
|
||||
void shouldTestrelativePathThrowsException() {
|
||||
Path relative = Path.of("relative-path.csv");
|
||||
|
||||
IllegalArgumentException ex = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> new StudentCsvReader(relative)
|
||||
);
|
||||
assertTrue(ex.getMessage().contains("absolute"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package ru.project.io;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
class StudentCsvWriterTest {
|
||||
|
||||
@TempDir Path tempDir;
|
||||
|
||||
@Test
|
||||
void writeShouldWriteHeaderAndStudentToEmptyFile() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
Files.createFile(file);
|
||||
|
||||
Student student = new Student("A12", 4.5, 10001);
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(student);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"), lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeShouldAppendStudentToExistingFile() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
|
||||
Files.write(file, List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"));
|
||||
|
||||
Student student = new Student("B07", 3.8, 10002);
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(student);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(
|
||||
List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001", "B07,3.8,10002"),
|
||||
lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeShouldNotCreateMissingFile() {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
|
||||
Student student = new Student("A12", 4.5, 10001);
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
assertThrows(IOException.class, () -> writer.write(student));
|
||||
|
||||
assertFalse(Files.exists(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeShouldAppendSeveralStudentsInOrder() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
Files.createFile(file);
|
||||
|
||||
Student student1 = new Student("A12", 4.5, 10001);
|
||||
Student student2 = new Student("B07", 3.8, 10002);
|
||||
Student student3 = new Student("C25", 4.9, 10003);
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(student1);
|
||||
writer.write(student2);
|
||||
writer.write(student3);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
"groupNumber,averageGrade,recordBookNumber",
|
||||
"A12,4.5,10001",
|
||||
"B07,3.8,10002",
|
||||
"C25,4.9,10003"),
|
||||
lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeArrayShouldAppendStudentsFromAllListsInOrder() throws Exception {
|
||||
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
Files.createFile(file);
|
||||
|
||||
Student student1 = new Student("A12", 4.5, 10001);
|
||||
Student student2 = new Student("B07", 3.8, 10002);
|
||||
Student student3 = new Student("C25", 4.9, 10003);
|
||||
Student student4 = new Student("D15", 4.2, 10004);
|
||||
Student student5 = new Student("E20", 3.6, 10005);
|
||||
|
||||
MyList<Student> firstList = new MyLinkedList<>();
|
||||
firstList.add(student1);
|
||||
firstList.add(student2);
|
||||
|
||||
MyList<Student> secondList = new MyLinkedList<>();
|
||||
secondList.add(student3);
|
||||
|
||||
MyList<Student> thirdList = new MyLinkedList<>();
|
||||
thirdList.add(student4);
|
||||
thirdList.add(student5);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MyList<Student>[] students = new MyList[] {firstList, secondList, thirdList};
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(students);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
"groupNumber,averageGrade,recordBookNumber",
|
||||
"A12,4.5,10001",
|
||||
"B07,3.8,10002",
|
||||
"C25,4.9,10003",
|
||||
"D15,4.2,10004",
|
||||
"E20,3.6,10005"),
|
||||
lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeArrayShouldSkipEmptyLists() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
Files.createFile(file);
|
||||
|
||||
Student student = new Student("A12", 4.5, 10001);
|
||||
|
||||
MyList<Student> emptyList = new MyLinkedList<>();
|
||||
|
||||
MyList<Student> nonEmptyList = new MyLinkedList<>();
|
||||
nonEmptyList.add(student);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MyList<Student>[] students = new MyList[] {emptyList, nonEmptyList, new MyLinkedList<>()};
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(students);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"), lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeEmptyArrayShouldNotModifyFile() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
|
||||
List<String> initialContent =
|
||||
List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001");
|
||||
|
||||
Files.write(file, initialContent);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MyList<Student>[] students = new MyList[0];
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(students);
|
||||
|
||||
assertEquals(initialContent, Files.readAllLines(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeEmptyArrayShouldNotCreateMissingFile() {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MyList<Student>[] students = new MyList[0];
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
assertThrows(IOException.class, () -> writer.write(students));
|
||||
|
||||
assertFalse(Files.exists(file));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package ru.project.random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class RandomStudentGeneratorTest {
|
||||
private static final Pattern GROUP_PATTERN = Pattern.compile("^[A-Z][0-9]{2}$");
|
||||
private static final double MIN_GRADE = 0.0;
|
||||
private static final double MAX_GRADE = 5.0;
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_withoutConstraints_returnsCorrectSizeAndValidStudents() {
|
||||
int size = 100;
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
MyList<Student> students = generator.generate(size);
|
||||
assertEquals(size, students.size(), "Размер списка должен совпадать с запрошенным");
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
assertTrue(
|
||||
GROUP_PATTERN.matcher(s.getGroupNumber()).matches(),
|
||||
"groupNumber должен соответствовать шаблону A00–Z99");
|
||||
assertTrue(
|
||||
s.getAverageGrade() >= MIN_GRADE && s.getAverageGrade() <= MAX_GRADE,
|
||||
"averageGrade должен быть в диапазоне [0.0, 5.0]");
|
||||
assertTrue(s.getRecordBookNumber() >= 1, "recordBookNumber должен быть >= 1");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_withGroupRange_respectsLexicographicalBounds() {
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator().setMinGroupNumber("A00").setMaxGroupNumber("B99");
|
||||
MyList<Student> students = generator.generate(200);
|
||||
assertEquals(200, students.size());
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
String g = s.getGroupNumber();
|
||||
assertTrue(g.compareTo("A00") >= 0, "Группа не должна быть меньше A00");
|
||||
assertTrue(g.compareTo("B99") <= 0, "Группа не должна быть больше B99");
|
||||
assertTrue(GROUP_PATTERN.matcher(g).matches(), "Группа должна соответствовать шаблону");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_withGradeRange_respectsBounds() {
|
||||
double minGrade = 3.0;
|
||||
double maxGrade = 4.0;
|
||||
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator().setMinAverageGrade(minGrade).setMaxAverageGrade(maxGrade);
|
||||
MyList<Student> students = generator.generate(100);
|
||||
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
double g = s.getAverageGrade();
|
||||
assertTrue(g >= minGrade && g <= maxGrade, "averageGrade должен быть в заданном диапазоне");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_withRecordBookRange_respectsBounds() {
|
||||
int minRec = 10_000;
|
||||
int maxRec = 20_000;
|
||||
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator().setMinRecordBookNumber(minRec).setMaxRecordBookNumber(maxRec);
|
||||
MyList<Student> students = generator.generate(100);
|
||||
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
int r = s.getRecordBookNumber();
|
||||
assertTrue(r >= minRec && r <= maxRec, "recordBookNumber должен быть в заданном диапазоне");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_negativeSize_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.generate(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinGroupNumber_invalidPattern_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMinGroupNumber("invalid"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMaxGroupNumber_invalidPattern_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> generator.setMaxGroupNumber("ZZ")); // не 2 цифры
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinMaxGroupNumber_minGreaterThanMax_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> generator.setMinGroupNumber("C00").setMaxGroupNumber("A99").generate(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinAverageGrade_outOfRange_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMinAverageGrade(-0.1));
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMinAverageGrade(5.1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMaxAverageGrade_outOfRange_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMaxAverageGrade(-0.1));
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMaxAverageGrade(5.1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinMaxAverageGrade_minGreaterThanMax_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> generator.setMinAverageGrade(4.0).setMaxAverageGrade(3.0).generate(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinRecordBookNumber_lessThanAllowed_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMinRecordBookNumber(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinMaxRecordBookNumber_minGreaterThanMax_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> generator.setMinRecordBookNumber(20_000).setMaxRecordBookNumber(10_000).generate(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestchainedBuilderStyle_worksAsExpected() {
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator()
|
||||
.setMinGroupNumber("A00")
|
||||
.setMaxGroupNumber("Z99")
|
||||
.setMinAverageGrade(2.0)
|
||||
.setMaxAverageGrade(4.0)
|
||||
.setMinRecordBookNumber(1_000)
|
||||
.setMaxRecordBookNumber(9_999);
|
||||
MyList<Student> students = generator.generate(50);
|
||||
assertEquals(50, students.size());
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
assertTrue(
|
||||
s.getGroupNumber().compareTo("A00") >= 0 && s.getGroupNumber().compareTo("Z99") <= 0);
|
||||
assertTrue(s.getAverageGrade() >= 2.0 && s.getAverageGrade() <= 4.0);
|
||||
assertTrue(s.getRecordBookNumber() >= 1_000 && s.getRecordBookNumber() <= 9_999);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,8 +78,9 @@ class SortStrategyTest {
|
||||
list.add(new Student("A05", 4.5, 5));
|
||||
list.add(new Student("A06", 4.5, 4));
|
||||
|
||||
EvenOnlyMergeSortStrategy<Student> strategy =
|
||||
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||
|
||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||
|
||||
int[] expected = {7, 2, 3, 4, 5, 8};
|
||||
@@ -95,8 +96,8 @@ class SortStrategyTest {
|
||||
list.add(new Student("A02", 4.5, 3));
|
||||
list.add(new Student("A03", 4.5, 5));
|
||||
|
||||
EvenOnlyMergeSortStrategy<Student> strategy =
|
||||
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||
|
||||
int[] expected = {1, 3, 5};
|
||||
@@ -112,8 +113,8 @@ class SortStrategyTest {
|
||||
list.add(new Student("A02", 4.5, 2));
|
||||
list.add(new Student("A03", 4.5, 6));
|
||||
|
||||
EvenOnlyMergeSortStrategy<Student> strategy =
|
||||
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||
|
||||
int[] expected = {2, 4, 6};
|
||||
|
||||
+143
-26
@@ -42,13 +42,13 @@ C25,4.9,10003
|
||||
|
||||
---
|
||||
|
||||
# `StudentCsvWriter`
|
||||
## `StudentCsvWriter`
|
||||
|
||||
## Назначение
|
||||
### Назначение
|
||||
|
||||
Дозаписывать одного студента в указанный CSV-файл.
|
||||
Дозаписывать одного студента в **существующий** CSV-файл.
|
||||
|
||||
## Контракт
|
||||
### Контракт
|
||||
|
||||
Конструктор:
|
||||
|
||||
@@ -65,41 +65,158 @@ public void write(Student student) throws IOException
|
||||
### Требования
|
||||
|
||||
1. Метод принимает одного `Student`.
|
||||
2. Запись выполняется в режиме добавления.
|
||||
3. Существующие записи не изменяются.
|
||||
4. Если файл существует, заголовок повторно не записывается.
|
||||
5. Если файл не существует, необходимо создать файл.
|
||||
6. При создании нового файла имя должно соответствовать формату:
|
||||
2. Указанный файл должен уже существовать.
|
||||
3. Запись выполняется в режиме добавления.
|
||||
4. Существующие записи не должны изменяться.
|
||||
5. Заголовок CSV не должен записываться повторно.
|
||||
6. Если файл не существует, новый файл **не создавать**.
|
||||
7. Если файл не существует, метод должен выбросить `IOException`.
|
||||
8. При успешной записи метод завершается без исключения.
|
||||
9. Метод не возвращает значение.
|
||||
10. Исключения, связанные с записью файла, не обрабатывать внутри метода.
|
||||
11. Исключения `Student` и `StudentBuilder` также не обрабатывать.
|
||||
|
||||
## Невозможность записи
|
||||
|
||||
Если указанный файл не существует, метод должен выбросить `IOException`.
|
||||
|
||||
Также `IOException` должен возникать и передаваться вызывающему коду, если:
|
||||
|
||||
- файл недоступен для записи;
|
||||
- отсутствуют права на запись;
|
||||
- произошла ошибка файловой системы;
|
||||
- запись в файл невозможна по другой причине.
|
||||
|
||||
Создание файла с именем вида:
|
||||
|
||||
```text
|
||||
students-yyyy-MM-dd-HH-mm.csv
|
||||
```
|
||||
|
||||
Например:
|
||||
`StudentCsvWriter` **не выполняет**.
|
||||
|
||||
```text
|
||||
students-2026-08-28-20-45.csv
|
||||
## Пример теста: несуществующий файл
|
||||
|
||||
Передать путь к несуществующему файлу:
|
||||
|
||||
```java
|
||||
writer.write(student);
|
||||
```
|
||||
|
||||
1. Новый файл создаётся в директории, указанной абсолютным путём.
|
||||
2. При создании нового файла сначала записывается заголовок.
|
||||
3. При успешной записи метод завершается без исключения.
|
||||
4. Метод не возвращает значение.
|
||||
5. Исключения, связанные с записью файла, не обрабатывать внутри метода — они должны передаваться вызывающему коду.
|
||||
6. Исключения `Student` и `StudentBuilder` также не обрабатывать.
|
||||
Проверить:
|
||||
|
||||
## Невозможность записи
|
||||
```text
|
||||
IOException
|
||||
```
|
||||
|
||||
Если студент не может быть записан, например:
|
||||
Проверить также, что новый файл не был создан.
|
||||
|
||||
- отсутствуют права на запись;
|
||||
- невозможно создать файл;
|
||||
- файл недоступен;
|
||||
- произошла ошибка файловой системы;
|
||||
## Изменение теста существующего файла
|
||||
|
||||
метод должен выбросить соответствующее исключение, например `IOException`.
|
||||
Для существующего файла:
|
||||
|
||||
Запись не должна возвращать специальное значение вроде `0` для обозначения ошибки.
|
||||
```text
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
A12,4.5,10001
|
||||
```
|
||||
|
||||
выполнить:
|
||||
|
||||
```java
|
||||
writer.write(student2);
|
||||
```
|
||||
|
||||
Проверить, что результат:
|
||||
|
||||
```text
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
A12,4.5,10001
|
||||
B07,3.8,10002
|
||||
```
|
||||
|
||||
Заголовок присутствует только один раз.
|
||||
|
||||
## Дополнительный метод `StudentCsvWriter`
|
||||
|
||||
Кроме метода записи одного студента:
|
||||
|
||||
```java
|
||||
public void write(Student student) throws IOException;
|
||||
```
|
||||
|
||||
необходимо реализовать метод:
|
||||
|
||||
```java
|
||||
public void write(MyList<Student>[] students) throws IOException;
|
||||
```
|
||||
|
||||
### Контракт
|
||||
|
||||
Метод должен:
|
||||
|
||||
1. Принимать массив `MyList<Student>`.
|
||||
2. Последовательно обрабатывать все переданные коллекции.
|
||||
3. Записывать каждого студента в указанный CSV-файл.
|
||||
4. Сохранять порядок:
|
||||
- коллекций в массиве;
|
||||
- студентов внутри каждой `MyList`.
|
||||
5. Использовать тот же формат CSV и те же правила записи, что и `write(Student)`.
|
||||
6. Работать только с уже существующим файлом.
|
||||
7. Не создавать файл, если он отсутствует.
|
||||
8. При отсутствии файла выбрасывать `IOException`.
|
||||
9. Не изменять переданные `MyList<Student>`.
|
||||
10. Не обрабатывать исключения, возникающие при создании или валидации `Student`.
|
||||
11. При ошибке записи прекращать операцию и передавать исключение вызывающему коду.
|
||||
|
||||
### Пример
|
||||
|
||||
```java
|
||||
MyList<Student>[] studentLists = ...;
|
||||
|
||||
writer.write(studentLists);
|
||||
```
|
||||
|
||||
Если:
|
||||
|
||||
```text
|
||||
studentLists[0] = [Student A, Student B]
|
||||
studentLists[1] = [Student C]
|
||||
studentLists[2] = [Student D, Student E]
|
||||
```
|
||||
|
||||
в файл должны последовательно добавиться:
|
||||
|
||||
```text
|
||||
Student A
|
||||
Student B
|
||||
Student C
|
||||
Student D
|
||||
Student E
|
||||
```
|
||||
|
||||
### Проверка пустых коллекций
|
||||
|
||||
Пустые `MyList<Student>` внутри массива не должны приводить к ошибке и не должны добавлять записей в файл.
|
||||
|
||||
### Проверка пустого массива
|
||||
|
||||
Для:
|
||||
|
||||
```java
|
||||
MyList<Student>[] studentLists = ...; // размер 0
|
||||
```
|
||||
|
||||
метод не должен добавлять никаких записей и не должен изменять файл.
|
||||
|
||||
### Проверка отсутствующего файла
|
||||
|
||||
Для несуществующего файла:
|
||||
|
||||
```java
|
||||
writer.write(studentLists);
|
||||
```
|
||||
|
||||
должен выбрасываться `IOException`. Файл создавать запрещено.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user