Merge branch 'develop' into feature/ui
This commit is contained in:
+12
@@ -42,12 +42,24 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-csv</artifactId>
|
||||||
|
<version>1.14.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.googlecode.lanterna</groupId>
|
<groupId>com.googlecode.lanterna</groupId>
|
||||||
<artifactId>lanterna</artifactId>
|
<artifactId>lanterna</artifactId>
|
||||||
<version>3.1.5</version>
|
<version>3.1.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-csv</artifactId>
|
||||||
|
<version>1.14.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<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;
|
package ru.project.finder;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import ru.project.collection.MyLinkedList;
|
import ru.project.collection.MyLinkedList;
|
||||||
import ru.project.collection.MyList;
|
import ru.project.collection.MyList;
|
||||||
import ru.project.student.Student;
|
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
|
@BeforeEach
|
||||||
{
|
void setUp() {
|
||||||
private StudentOccurrenceIndexFinder finder;
|
finder = new StudentOccurrenceIndexFinder();
|
||||||
private MyList<Student> students;
|
students = new MyLinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@Test
|
||||||
void setUp() {
|
void shouldTesfindOccurrences_emptyList_returnsEmptyArray() {
|
||||||
finder = new StudentOccurrenceIndexFinder();
|
int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1));
|
||||||
students = new MyLinkedList<>();
|
assertEquals(0, result.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldTesfindOccurrences_emptyList_returnsEmptyArray() {
|
void shouldTesfindOccurrences_noMatches_returnsEmptyArray() {
|
||||||
int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1));
|
students.add(new Student("A12", 4.0, 101));
|
||||||
assertEquals(0, result.length);
|
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);
|
int[] result = finder.findOccurrences(students, target);
|
||||||
assertEquals(0, result.length);
|
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));
|
|
||||||
|
|
||||||
Student target = new Student("B23", 3.5, 102);
|
@Test
|
||||||
int[] result = finder.findOccurrences(students, target);
|
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);
|
Student target = new Student("B23", 3.5, 102);
|
||||||
assertEquals(1, result[0]);
|
int[] result = finder.findOccurrences(students, target);
|
||||||
}
|
|
||||||
@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("A12", 4.5, 101);
|
assertEquals(1, result.length);
|
||||||
int[] result = finder.findOccurrences(students, target);
|
assertEquals(1, result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(3, result.length);
|
@Test
|
||||||
assertArrayEquals(new int[]{0, 2, 4}, result);
|
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
|
Student target = new Student("A12", 4.5, 101);
|
||||||
void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() {
|
int[] result = finder.findOccurrences(students, target);
|
||||||
Student s = new Student("X99", 5.0, 999);
|
|
||||||
students.add(s);
|
|
||||||
students.add(s);
|
|
||||||
students.add(s);
|
|
||||||
|
|
||||||
int[] result = finder.findOccurrences(students, s);
|
assertEquals(3, result.length);
|
||||||
assertEquals(3, result.length);
|
assertArrayEquals(new int[] {0, 2, 4}, result);
|
||||||
assertArrayEquals(new int[]{0, 1, 2}, result);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldTesfindOccurrences_differentAverageGrade_noMatch() {
|
void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() {
|
||||||
students.add(new Student("A12", 4.50, 101));
|
Student s = new Student("X99", 5.0, 999);
|
||||||
Student target = new Student("A12", 4.51, 101); // отличается балл
|
students.add(s);
|
||||||
|
students.add(s);
|
||||||
|
students.add(s);
|
||||||
|
|
||||||
int[] result = finder.findOccurrences(students, target);
|
int[] result = finder.findOccurrences(students, s);
|
||||||
assertEquals(0, result.length);
|
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));
|
students.add(new Student("A12", 4.5, 101));
|
||||||
Student target = new Student("B12", 4.5, 101); // другая группа
|
} else {
|
||||||
|
students.add(new Student(group, grade, 200 + i));
|
||||||
int[] result = finder.findOccurrences(students, target);
|
}
|
||||||
assertEquals(0, result.length);
|
|
||||||
}
|
}
|
||||||
|
Student target = new Student("A12", 4.5, 101);
|
||||||
|
int[] result = finder.findOccurrences(students, target);
|
||||||
|
|
||||||
@Test
|
for (int i = 1; i < result.length; i++) {
|
||||||
void shouldTesfindOccurrences_differentRecordBookNumber_noMatch() {
|
assertTrue(result[i] > result[i - 1], "Результат должен быть отсортирован");
|
||||||
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], "Результат должен быть отсортирован");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("A05", 4.5, 5));
|
||||||
list.add(new Student("A06", 4.5, 4));
|
list.add(new Student("A06", 4.5, 4));
|
||||||
|
|
||||||
EvenOnlyMergeSortStrategy<Student> strategy =
|
EvenOnlySortStrategy<Student> strategy =
|
||||||
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||||
|
|
||||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||||
|
|
||||||
int[] expected = {7, 2, 3, 4, 5, 8};
|
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("A02", 4.5, 3));
|
||||||
list.add(new Student("A03", 4.5, 5));
|
list.add(new Student("A03", 4.5, 5));
|
||||||
|
|
||||||
EvenOnlyMergeSortStrategy<Student> strategy =
|
EvenOnlySortStrategy<Student> strategy =
|
||||||
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||||
|
|
||||||
int[] expected = {1, 3, 5};
|
int[] expected = {1, 3, 5};
|
||||||
@@ -112,8 +113,8 @@ class SortStrategyTest {
|
|||||||
list.add(new Student("A02", 4.5, 2));
|
list.add(new Student("A02", 4.5, 2));
|
||||||
list.add(new Student("A03", 4.5, 6));
|
list.add(new Student("A03", 4.5, 6));
|
||||||
|
|
||||||
EvenOnlyMergeSortStrategy<Student> strategy =
|
EvenOnlySortStrategy<Student> strategy =
|
||||||
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||||
|
|
||||||
int[] expected = {2, 4, 6};
|
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`.
|
1. Метод принимает одного `Student`.
|
||||||
2. Запись выполняется в режиме добавления.
|
2. Указанный файл должен уже существовать.
|
||||||
3. Существующие записи не изменяются.
|
3. Запись выполняется в режиме добавления.
|
||||||
4. Если файл существует, заголовок повторно не записывается.
|
4. Существующие записи не должны изменяться.
|
||||||
5. Если файл не существует, необходимо создать файл.
|
5. Заголовок CSV не должен записываться повторно.
|
||||||
6. При создании нового файла имя должно соответствовать формату:
|
6. Если файл не существует, новый файл **не создавать**.
|
||||||
|
7. Если файл не существует, метод должен выбросить `IOException`.
|
||||||
|
8. При успешной записи метод завершается без исключения.
|
||||||
|
9. Метод не возвращает значение.
|
||||||
|
10. Исключения, связанные с записью файла, не обрабатывать внутри метода.
|
||||||
|
11. Исключения `Student` и `StudentBuilder` также не обрабатывать.
|
||||||
|
|
||||||
|
## Невозможность записи
|
||||||
|
|
||||||
|
Если указанный файл не существует, метод должен выбросить `IOException`.
|
||||||
|
|
||||||
|
Также `IOException` должен возникать и передаваться вызывающему коду, если:
|
||||||
|
|
||||||
|
- файл недоступен для записи;
|
||||||
|
- отсутствуют права на запись;
|
||||||
|
- произошла ошибка файловой системы;
|
||||||
|
- запись в файл невозможна по другой причине.
|
||||||
|
|
||||||
|
Создание файла с именем вида:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
students-yyyy-MM-dd-HH-mm.csv
|
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