JavaRush /Курсы /Модуль 3. Java Professional /Проект по теме: тестирование и логирование

Проект по теме: тестирование и логирование

Модуль 3. Java Professional
5 уровень , 7 лекция
Открыта

Дана программа – иммитация ипподрома

Для начала, как и в прошлом проекте, сделай себе форк из репозитория: https://github.com/pavlo-plynko/hippodrome, и этот форк склонируй себе.

Твоя задача – добавить тестирование и логирование.

Список необходимых тестов

В следующем списке каждый пункт нужно реализовать как отдельный тестовый метод. Придумывая имена тестовым методам, старайся быть лаконичным, но в то же время чтобы можно было примерно понять, что именно в них тестируется. К примеру, сравни эти два результата тестирования:

Во втором случае труднее понять, что за тесты не прошли, и придется смотреть логи.

1. Класс Horse:

  1. конструктор
    • Проверить, что при передаче в конструктор первым параметром null, будет выброшено IllegalArgumentException. Для этого нужно воспользоваться методом assertThrows;
    • Проверить, что при передаче в конструктор первым параметром null, выброшенное исключение будет содержать сообщение "Name cannot be null.". Для этого нужно получить сообщение из перехваченного исключения и воспользоваться методом assertEquals;
    • Проверить, что при передаче в конструктор первым параметром пустой строки или строки содержащей только пробельные символы (пробел, табуляция и т.д.), будет выброшено IllegalArgumentException. Чтобы выполнить проверку с разными вариантами пробельных символов, нужно сделать тест параметризованным;
    • Проверить, что при передаче в конструктор первым параметром пустой строки или строки содержащей только пробельные символы (пробел, табуляция и т.д.), выброшенное исключение будет содержать сообщение "Name cannot be blank.";
    • Проверить, что при передаче в конструктор вторым параметром отрицательного числа, будет выброшено IllegalArgumentException;
    • Проверить, что при передаче в конструктор вторым параметром отрицательного числа, выброшенное исключение будет содержать сообщение "Speed cannot be negative.";
    • Проверить, что при передаче в конструктор третьим параметром отрицательного числа, будет выброшено IllegalArgumentException;
    • Проверить, что при передаче в конструктор третьим параметром отрицательного числа, выброшенное исключение будет содержать сообщение "Distance cannot be negative.";
  2. метод getName
    • Проверить, что метод возвращает строку, которая была передана первым параметром в конструктор;
  3. метод getSpeed
    • Проверить, что метод возвращает число, которое было передано вторым параметром в конструктор;
  4. метод getDistance
    • Проверить, что метод возвращает число, которое было передано третьим параметром в конструктор;
    • Проверить, что метод возвращает ноль, если объект был создан с помощью конструктора с двумя параметрами;
  5. метод move
    • Проверить, что метод вызывает внутри метод getRandomDouble с параметрами 0.2 и 0.9. Для этого нужно использовать MockedStatic и его метод verify;
    • Проверить, что метод присваивает дистанции значение высчитанное по формуле: distance + speed * getRandomDouble(0.2, 0.9). Для этого нужно замокать getRandomDouble, чтобы он возвращал определенные значения, которые нужно задать параметризовав тест.

2. Класс Hippodrome:

  1. Конструктор
    • Проверить, что при передаче в конструктор null, будет выброшено IllegalArgumentException;
    • Проверить, что при передаче в конструктор null, выброшенное исключение будет содержать сообщение "Horses cannot be null.";
    • Проверить, что при передаче в конструктор пустого списка, будет выброшено IllegalArgumentException;
    • Проверить, что при передаче в конструктор пустого списка, выброшенное исключение будет содержать сообщение "Horses cannot be empty.";
  2. метод getHorses
    • Проверить, что метод возвращает список, который содержит те же объекты и в той же последовательности, что и список который был передан в конструктор. При создании объекта Hippodrome передай в конструктор список из 30 разных лошадей;
  3. метод move
    • Проверить, что метод вызывает метод move у всех лошадей. При создании объекта Hippodrome передай в конструктор список из 50 моков лошадей и воспользуйся методом verify.
  4. метод getWinner
    • Проверить, что метод возвращает лошадь с самым большим значением distance.

3. Класс Main

  1. метод main
    • Проверить, что метод выполняется не дольше 22 секунд. Для этого воспользуйся аннотацией Timeout. После написания этого теста, отключи его (воспользуйся аннотацией Disabled). Так он не будет занимать время при запуске всех тестов, а при необходимости его можно будет запустить вручную.

Что нужно логировать

1. Класс Main:

  1. После создания объекта ипподрома, добавить в лог запись вида: 2022-05-31 17:05:26,152 INFO Main: Начало скачек. Количество участников: 7
  2. После вывода информации о победителе, добавить в лог запись вида: 2022-05-31 17:05:46,963 INFO Main: Окончание скачек. Победитель: Вишня

Класс Hippodrome:

  1. Если в конструктор был передан null, то перед пробросом исключения, добавить в лог запись вида: 2022-05-31 17:29:30,029 ERROR Hippodrome: Horses list is null
  2. b. Если в конструктор был передан пустой список, то перед пробросом исключения, добавить в лог запись вида: 2022-05-31 17:30:41,074 ERROR Hippodrome: Horses list is empty
  3. В конце конструктора добавить в лог запись вида: 2022-05-31 17:05:26,152 DEBUG Hippodrome: Создание Hippodrome, лошадей [7]

3. Класс Horse:

  1. Если в конструктор вместо имени передан null, то перед пробросом исключения, добавить в лог запись вида: 2022-05-31 17:34:59,483 ERROR Horse: Name is null
  2. Если переданное в конструктор имя пустое, то перед пробросом исключения, добавить в лог запись вида: 2022-05-31 17:36:44,196 ERROR Horse: Name is blank
  3. Если переданная в конструктор скорость меньше нуля, то перед пробросом исключения, добавить в лог запись вида:2022-05-31 17:40:27,267 ERROR Horse: Speed is negative
  4. Если переданная в конструктор дистанция меньше нуля, то перед пробросом исключения, добавить в лог запись вида: 2022-05-31 17:41:21,938 ERROR Horse: Distance is negative
  5. В конце конструктора добавить в лог запись вида: 2022-05-31 17:15:25,842 DEBUG Horse: Создание Horse, имя [Лобстер], скорость [2.8]

Логи должны писаться в файл hippodrome.log, который должен располагаться в корне проекта в папке logs. Каждый день файл должен переименовываться по шаблону в hippodrome.2021-12-31.log и вместо него должен создаваться новый hippodrome.log. Для этого используй аппендер RollingFile. При этом файлы старше 7 дней, должны удаляться. Для этого можешь использовать конструкцию вида:


<DefaultRolloverStrategy>
    <Delete …>
        <IfFileName …/>
        <IfLastModified …/>
    </Delete>
</DefaultRolloverStrategy>

Погугли, что нужно подставить вместо троеточий.😊


Разбор проекта. Смотреть после выполнения!

Комментарии (26)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ardan Уровень 57
7 сентября 2025
Помогло https://www.baeldung.com/mockito-mock-static-methods
Антон В. Уровень 115
1 марта 2025
По методу метода move() ребята ниже ХОРОШО подсказали. Не знаю точно, какое решение правильно. Я на них просто смотрел, но не копировал. Думаю, что этих примеров + ИНТЕРНЕТ достаточно, чтобы РАЗОБРАТЬСЯ и написать свое решение.
Иван Уровень 109
10 октября 2024
А как проект на проверку отправить?
Адам Уровень 80 Expert
19 августа 2024
В разборе проекта я не увидел аннотацию @ExtendWith(MockitoExtension.class) перед классами HippodromeTest и HorseTest но тесты с мохито всё равно работают, то есть они добавляются автоматический неявно ?
Алексей Уровень 86 Expert
7 августа 2024
Кто то сталкивался что логгер игнорирует файл с настройками log4j2.xml ? файл находится в директории resources.
Артем Уровень 72
11 августа 2024
не уверен на все 100%, но я думаю, тебе нужно убрать статус warn из тега configuration. В лекции было написано, что конфигурация с этим статусом будет игнорировать сообщения статусом debug и info, которые в этом задании как раз и используются
Алексей Уровень 86 Expert
13 августа 2024
Пробовал, не помогает
Александр Уровень 115
20 декабря 2024
Тоже столкнулся с этим. Во-первых, он почему-то не видит properties - только xml. Во-вторых, нужна только одна зависимость

<dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.23.1</version>
        </dependency>
Иван Корниенко Уровень 109
14 мая 2024
Раньше мог наговнокодить. Теперь могу наговнологить 💩
Kirill Уровень 106 Expert
17 мая 2024
Осталось научиться еще говнотестить и супер специалисты готовы покорять рынок труда )
Дмитрий Уровень 66 Expert
14 декабря 2025
😂
Рустам Уровень 115
13 февраля 2024
В задании про конструктор Horse, ошибка: "Проверить, что метод присваивает дистанции значение высчитанное по формуле: distance + speed * getRandomDouble(0.2, 0.9). Для этого нужно замокать getRandomDouble, чтобы он возвращал определенные значения, которые нужно задать параметризовав тест.". Должно быть distance += speed * getRandomDouble(0.2, 0.9).
Taurnil Уровень 107 Expert
15 февраля 2024
Это одно и тоже: distance += speed * getRandomDouble(0.2, 0.9) distance = distance + speed * getRandomDouble(0.2, 0.9) "distance =" эквивалентно "присваивает дистанции значение" Если написать "...метод присваивает дистанции значение высчитанное по формуле: distance += speed * getRandomDouble(0.2, 0.9)..." получится масло масляное :)
Ольга Николенко Уровень 109 Expert
15 февраля 2024
Думаю фраза: присваивает дистанции значение высчитанное по формуле: distance + speed * getRandomDouble(0.2, 0.9) эквивалентна записи: distance = distance + speed * getRandomDouble(0.2, 0.9) что соответственно эквивалентно: distance += speed * getRandomDouble(0.2, 0.9)
Рустам Уровень 115
18 февраля 2024
Действительно, неправильно прочел.
Александр Уровень 83
2 октября 2023
Если у вас мавен не видит тесты, с чем и я сталкивался, то вот плагин, который вам поможет - <directory>${project.basedir}/target</directory> <outputDirectory>${project.build.directory}/classes</outputDirectory> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> </plugins> Если версия подсвечивается красным, то плагин всё равно работает. Однако, даже после установки плагина у меня при запуске тестов напрямую из класса всё равно пишет, что тесты не были найдены, хоть и mvn test отлично справляется. Если кто-то поможет решить данную проблему, буду рад, т.к. гугл мне с этим не смог помочь.
Дмитрий Уровень 117 Expert
10 сентября 2023
Очень режет запятая между подлежащим и сказуемым в предложении "При этом файлы старше 7 дней, должны удаляться." Очевидно, ошибка. Админы, исправьте пожалуйста. https://cyberleninka.ru/article/n/otsutstvie-zapyatoy-mezhdu-podlezhaschim-i-skazuemym-samoochevidnaya-norma-ili-lakuna-v-pravilah/viewer
Антон Уровень 63 Expert
14 августа 2023
В разборе что-то нету как правильно было сделать вторую часть тестов метода move в классе Horse, может кто-то может прислать как сделали, если у вас проверял ментор?

@ParameterizedTest
    @ValueSource(doubles = {0.3, 0.4, 0.5})
    void checkGetRandomDoubleMethod(double arg) {
        try (MockedStatic<Horse> mockObject =  mockStatic(Horse.class)) {
            mockObject.when(() -> Horse.getRandomDouble(0.2, 0.9)).thenReturn(arg);
            Horse horse = new Horse("Name", 2, 3);
            Double probMove = horse.getDistance() + horse.getSpeed() * Horse.getRandomDouble(0.2, 0.9);
            horse.move();
            assertEquals(probMove, horse.getDistance());
        }
    }
Я сделал так, но не уверен, что это хорошо...
Владислав Уровень 82 Expert
2 сентября 2023
если ты сам это сделал, то можешь сказать как до этого дошел? я не понимаю как такое гуглить
Валерия Уровень 55 Expert
15 октября 2023
Я реализовала следующим образом

@ParameterizedTest
    @CsvSource({
            "0.3, 1.9",
            "0.4, 2.2",
            "0.5, 2.5"
    })
    public void moveUsesGetRandomMock(double arg, double res) throws NoSuchFieldException, IllegalAccessException {
        try (MockedStatic<Horse> mockedStatic = mockStatic(Horse.class)){
            mockedStatic.when(() -> Horse.getRandomDouble(0.2, 0.9)).thenReturn(arg);
            Horse horse = new Horse("name", 3, 1);
            horse.move();
            Field field = Horse.class.getDeclaredField("distance");
            field.setAccessible(true);
            System.out.println(res + " " + field.get(horse));
            assertEquals(res, field.get(horse));
        }
    }
Павел Уровень 111 Expert
30 октября 2023
Сделал так, но тоже не уверен что правильно.

@ParameterizedTest
    @ValueSource(doubles = {0.3, 0.5, 0.7, 0.8})
    void testMoveDistance(double arg){
        try(MockedStatic<Horse> mockedStatic = mockStatic(Horse.class)){
            when(Horse.getRandomDouble(0.2, 0.9)).thenReturn(arg);
            Horse horse = new Horse("Horse", 1.0);

            horse.move();

            assertEquals(horse.getSpeed()*arg, horse.getDistance());
        }
    }
Роман Уровень 106 Expert
18 апреля 2024
1:12:20 на видео ментор вспоминает, что пропустили эту проверку) Оставлю это здесь для потомков
Ислам Уровень 62 Expert
19 августа 2024
В задании сказано, что нужно было использовать метод verify(), вы же этот метод не использовали, а использовали рефлексию и тем самым усложнили свой код. Лично я решил так:

    @ParameterizedTest
    @ValueSource(doubles = {0.3, 0.7, 0.9})
    void testMoveWithParameterized(double mockRandomValue) {
        
        try (MockedStatic<Horse> mocked = mockStatic(Horse.class)) {
            mocked.when(() -> Horse.getRandomDouble(0.2, 0.9)).thenReturn(mockRandomValue);
            Horse horse = new Horse("Roach", 16.0, 10.0);
            // Выполняем метод move
            horse.move();
            assertEquals(10.0 + 16.0 * mockRandomValue, horse.getDistance());
            mocked.verify(() -> Horse.getRandomDouble(0.2, 0.9));
        }
    }