Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,20 @@ repositories {
}

dependencies {
testImplementation 'org.mockito:mockito-core:5.14.2'
testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.4'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.4'
}

test {
systemProperty "file.encoding", "utf-8"
useJUnitPlatform()
}

compileJava.options.encoding = 'UTF-8'

tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
3 changes: 2 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Tue Feb 11 23:22:06 MSK 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
rootProject.name = 'unit-testing-practical-task'


Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лишняя пустая строка

65 changes: 0 additions & 65 deletions src/main/java/com/walking/lesson125_unit_testing/Main.java
Original file line number Diff line number Diff line change
@@ -1,77 +1,12 @@
package com.walking.lesson125_unit_testing;


import com.walking.lesson125_unit_testing.exception.RegexValidationException;
import com.walking.lesson125_unit_testing.model.FullName;

/**
* На базе вашего решения
* <a href="https://github.com/KFalcon2022/unit-testing-practical-task">задачи из урока 125</a>
* актуализируйте юнит-тесты с использованием Mockito. Сложность задачи напрямую зависит от качества декомпозиции в
* вашем предыдущем решении.
*/
public class Main {

public static final String FULL_NAME_REGEX = "^[А-Я][А-Яа-я-]* [А-Я][а-я]* [А-Я][а-я]+$";
public static final String DOUBLE_SURNAME_REGEX = "[А-Я][а-я]*-[А-Я][а-я]*";
public static final String NAME_REGEX = "[А-Я][а-я]*";
public static final String PATRONYMIC_REGEX = "[А-Я][а-я]+";

public static void main(String[] args) {
System.out.println(parseName("Иванов Иван Иванович"));
System.out.println(parseName("Иванов-Иванов Иван Иванович"));
System.out.println(parseName("Иванов-Иванов И Иванович"));
System.out.println(parseName("И-Иванов И Иванович"));
System.out.println(parseName("Иванов иван Иванович"));
// Все равно упадет на 30й строке.
// System.out.println(parseName("И-иванов И Иванович"));
// System.out.println(parseName("Иванов Иван иванович"));
// System.out.println(parseName("ИваНов Иван Иванович"));
// System.out.println(parseName("Ivanov Ivan"));
}

private static FullName parseName(String nameString) {
if (!nameString.matches(FULL_NAME_REGEX)) {
throw new RegexValidationException(nameString, FULL_NAME_REGEX);
}

FullName fullName = new FullName();
String[] splitNameString = nameString.split(" ");

String surname = splitNameString[0];
validateSurname(surname);
fullName.setSurname(surname);

String name = splitNameString[1];
validateName(name);
fullName.setName(name);

String patronymic = splitNameString[2];
validatePatronymic(patronymic);
fullName.setPatronymic(patronymic);

return fullName;
}

private static void validateSurname(String surname) {
if (surname.contains("-")) {
if (!surname.matches(DOUBLE_SURNAME_REGEX)) {
throw new RegexValidationException(surname, DOUBLE_SURNAME_REGEX);
}
} else {
validateName(surname);
}
}

private static void validateName(String name) {
if (!name.matches(NAME_REGEX)) {
throw new RegexValidationException(name, NAME_REGEX);
}
}

private static void validatePatronymic(String name) {
if (!name.matches(PATRONYMIC_REGEX)) {
throw new RegexValidationException(name, PATRONYMIC_REGEX);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.walking.lesson125_unit_testing.model;

import java.util.Objects;

public class FullName {
private String name;
private String surname;
Expand Down Expand Up @@ -42,4 +44,26 @@ public void setPatronymic(String patronymic) {
public String toString() {
return "%s %s %s".formatted(surname, name, patronymic);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

FullName fullName = (FullName) o;
return Objects.equals(name, fullName.name) && Objects.equals(surname, fullName.surname) && Objects.equals(
patronymic, fullName.patronymic);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше сразу часть выражения перенести на следующую строку:

&& Objects.equals(patronymic, fullName.patronymic)

Всегда усложняет восприятие, если переносишь что-то вложенное, как здесь

}

@Override
public int hashCode() {
int result = Objects.hashCode(name);
result = 31 * result + Objects.hashCode(surname);
result = 31 * result + Objects.hashCode(patronymic);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.walking.lesson125_unit_testing.service;

import com.walking.lesson125_unit_testing.exception.RegexValidationException;
import com.walking.lesson125_unit_testing.model.FullName;

public class FullNameParsingService {
private final FullNameValidationService fullNameValidationService;

public FullNameParsingService(FullNameValidationService fullNameValidationService) {
this.fullNameValidationService = fullNameValidationService;
}

public FullName parseFullName(String nameString) {
try {
fullNameValidationService.validateFullName(nameString);

String[] splitNameString = nameString.split(" ");

String surname = splitNameString[0];
fullNameValidationService.validateSurname(surname);

String name = splitNameString[1];
fullNameValidationService.validateName(name);

String patronymic = splitNameString[2];
fullNameValidationService.validatePatronymic(patronymic);

return new FullName(name, surname, patronymic);
} catch (RegexValidationException e) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В чем смысл try-catch? Ты ведь буквально подменяешь один RuntimeException другим

В целом, явное отлавливание RuntimeException в catch будет некорректным подходом в абсолютном большинстве случаев. Есть ситуации, когда это оправдано, но когда ты с ними столкнешься - скорее всего уже придет понимание, зачем это делать

throw new RuntimeException("Failed parsing fullName", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.walking.lesson125_unit_testing.service;

import com.walking.lesson125_unit_testing.exception.RegexValidationException;

public class FullNameValidationService {
public static final String FULL_NAME_REGEX = "^[А-Я][А-Яа-я-]* [А-Я][а-я]* [А-Я][а-я]+$";
public static final String DOUBLE_SURNAME_REGEX = "[А-Я][а-я]*-[А-Я][а-я]*";
public static final String NAME_REGEX = "[А-Я][а-я]*";
public static final String PATRONYMIC_REGEX = "[А-Я][а-я]+";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если константа применяется только внутри класса - почему public?


public FullNameValidationService() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

конструктор по умолчанию будет создан и без твоего участия

}

public void validateFullName(String nameString) {
if (!nameString.matches(FULL_NAME_REGEX)) {
throw new RegexValidationException(nameString, FULL_NAME_REGEX);
}
}

public void validateSurname(String surname) {
if (surname.contains("-")) {
if (!surname.matches(DOUBLE_SURNAME_REGEX)) {
throw new RegexValidationException(surname, DOUBLE_SURNAME_REGEX);
}
} else {
validateName(surname);
}
}

public void validateName(String name) {
if (!name.matches(NAME_REGEX)) {
throw new RegexValidationException(name, NAME_REGEX);
}
}

public void validatePatronymic(String name) {
if (!name.matches(PATRONYMIC_REGEX)) {
throw new RegexValidationException(name, PATRONYMIC_REGEX);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.walking.lesson125_unit_testing.service;

import com.walking.lesson125_unit_testing.exception.RegexValidationException;
import com.walking.lesson125_unit_testing.model.FullName;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class FullNameParsingServiceTest {
@InjectMocks
private FullNameParsingService fullNameParsingService;

@Mock
private FullNameValidationService fullNameValidationService;

@Test
void parseFullName_success() {
// given
String testFullNameString = "Иванов Иван Иванович";
FullName validFullName = new FullName("Иван", "Иванов", "Иванович");
// when
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

пустая строка перед комментом. Логика простая: мы отделяем логические блоки кода пустой строкой. Коммент к строке кода или к блоку - часть этого блока

FullName parsedFullName = fullNameParsingService.parseFullName(testFullNameString);
// then
assertEquals(validFullName, parsedFullName);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Опциональная рекомендация (здесь скорее не нужна): для однообразия тестов и более прозрачных инструкций можно явно прописывать конфигурацию мока на стандартное поведение (условно, doNothing().when(...). Бывает полезно, когда логика запутанная и тестовых методов много и они все равно объемные. Помогает читающего и отлаживающему понять, в чем специфичность тестируемого сценария

В простых случаях, как здесь, это избыточно


@Test
void parseFullName_fail() {
// given
Mockito.doThrow(RegexValidationException.class)
.when(fullNameValidationService)
.validateFullName(Mockito.anyString());
// when
Executable actual = () -> fullNameParsingService.parseFullName(Mockito.any());
// then
assertThrows(RuntimeException.class, actual);
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

пустая строка в конце файла

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В классе ниже тоже потерялась

Loading