Как писать хорошие модульные тесты: макет из базы данных + не используйте @SpringBootTest «Коллекция предложений».
Как писать хорошие модульные тесты: макет из базы данных + не используйте @SpringBootTest «Коллекция предложений».

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

Обновлено 25 марта 2022 г.: Люди, которые считают, что Mock не нужен, вероятно, не выполняли несколько командных проектов и не сталкивались с вызовами между службами. Неважно, люди всегда взрослеют. Если вы столкнетесь с этим в будущем, я буду благодарен вам за то, что вы прочитали эту статью сейчас.

Уведомление:Если следующий контент подключенданныебиблиотечный блоктестошибка,Это моя ошибка. Потому что я уже много лет не занимался самостоятельными проектами.,Все мультисервисные,УТ все высмеивают.

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

Дополнение: при появлении в коде нового объекта PowerMockito.whenNew(entityDao.class).withAnyArguments().thenReturn(entity);

метод void может использовать ничего не делая

Оглавление

1. Общий метод написания модульных тестов

2. Этапы модульного тестирования

3. Анализируйте и оптимизируйте общие методы написания модульных тестов.

4. Лучший способ написания модульных тестов: макетировать базу данных + не запускать Spring + оптимизировать скорость тестирования + не вводить компоненты проекта.

1. Общие методы модульного тестирования

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

Язык кода:javascript
копировать
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Transactional
@Rollback(true) // Транзакции автоматически откатываются. Значение по умолчанию — true. Нет необходимости писать
public class HelloServiceTest {

    @Autowired
    private HelloService helloService;

    @Test
    public void sayHello() {
        helloService.sayHello("zhangsan");
    }

В этом примере 4 ошибки: (Все ошибки в этой статье относятся к нестандартным. На самом деле писать такие модульные тесты можно, но это не стандартизировано и не демонстрирует отличных навыков программирования всех здесь присутствующих. )

1. @Autowired запускает Spring

2. @SpringBootTest запускает среду SpringBoot, а groups=Application.class запускает весь проект.

3. Вы можете узнать, что база данных вызывается через @Transactional

4. Нет утверждения Assert

2. Распространенные неправильные шаги модульного тестирования (в среде SpringBoot)

1、использовать@RunWith(SpringRunner.class)указано вSpringосуществляется в среде Юнит-тест,такSpringфазасосредоточиться решение будет признано и вступит в силу

2、Затемиспользовать@SpringBootTest,Он сканирует весеннюю конфигурацию приложения.,И создайте полный контекст Spring.

3. Через @SpringBootTest мы можем указать класс запуска,или дать@SpringBootTestПараметрыwebEnvironmentнаделять ЗначениеSpringBootTest.WebEnvironment.RANDOM_PORT,так ВоляЗапустите веб-контейнер и прослушайте случайный порт.,в то же время,для насАвтоматически подключить компонент типа TestRestTemplate.чтобы помочь нам отправитьтестпросить。

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

Или ваш проект использует аннотацию @Component (она будет создана/запущена при запуске проекта SpringBoot)

Класс запуска также определяет классы, экземпляры которых создаются при запуске.

В этом аннотированном классе @Component есть несколько методов потока.,С классом ApplicationStartup, определенным в классе запуска, началось,ТакПри выполнении модульного тестирования из-за влияния многопоточных задач данные в вашей базе данных могут быть изменены, даже если вы используете аннотацию отката транзакции @Transactional.。Проблема у меня есть:Когда я бегу Юнит-тесткогда,Другие классы в коде продолжают получать сообщения activeMQ в многопоточном режиме.,Затем обновите соответствующие данные в библиотеке данных. Перекрывается с процессом выполнения модульного теста,Вызывает сбой модульного теста. Когда другие члены команды работают с библиотекой данных,Еще потому, что я случайно запустил многопоточность и изменил библиотеку данных.,Вызвал трудности в разработке.

Кроме того, прилагается исходный код @Component, так что вы можете узнать его кстати.

Язык кода:javascript
копировать
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
    //Это значение может использоваться как имя логического компонента (т.е. класса) и конвертироваться в Spring во время автоматического сканирования bean,
    //Это эквивалентно<bean id="" class="" />вid
    String value() default "";
}

@Component — это метааннотация, что означает, что можно аннотировать другие аннотации классов, например @Controller @Service @Repository @Aspect. Официальные слова таковы: классы с этой аннотацией считаются компонентами. При использовании конфигурации и сканирования путей к классам на основе этой аннотации будут созданы экземпляры этих классов. Другие аннотации уровня класса также можно идентифицировать как специальный тип компонента, например @Repository @Aspect. Следовательно, @Component может аннотировать другие аннотации классов.

3. Оптимизировать метод написания модульных тестов

Позвольте мне сначала взглянуть на рисунок выше, чтобы показать, сколько времени занимает запуск модульного теста. Затем мы сравниваем и выбираем лучший способ написания модульных тестов. На моем 6-летнем ноутбуке запуск модульного теста занимает почти минуту, но после оптимизации кода это занимает всего несколько секунд. Вот как оптимизировать:

первый,Нам необходимо уточнить конечную цель юнит-теста.,то естьПолное отсутствие в базе данныхПолное отсутствие в базе данныхПолное отсутствие в базе данных!Во-вторых,Юнит-тест предназначен для тестирования только одного метода (небольшого модуля) определенного класса.,В процессе теста,Давай больше ничего не начнем,Быть свободным от возможного вмешательства других факторов в проект.

Так что можно обнаружить, что приведенный выше пример — просто оскорбление юнит-теста, и так его пишут только самые младшие школьники. Как мы все знаем, каждый здесь способен стать архитектором. Далее мы будем критиковать этот ошибочный юнит-тест построчно, пять раз в секунду:

1. Не следует использовать@Autowired

Язык кода:javascript
копировать
@Autowired
private HelloService helloService;

Этот @Autowired просто лишний! Вот с чего начинается весна. Когда раньше не было @Autowired, нам нужно было настроить свойства bean-компонента следующим образом:

Язык кода:javascript
копировать
<property name="имя атрибута" value=" значение атрибута"/>

Этот метод содержит много кода и громоздкую настройку, поэтому в Spring 2.5 появилась аннотация @Autowired.

Принцип @Autowired

При запуске Spring IOC контейнер автоматически загружает постпроцессор AutowiredAnnotationBeanPostProcessor. Когда контейнер сканирует @Autowied, @Resource или @Inject, он автоматически находит необходимый bean-компонент в контейнере IOC и собирает атрибуты объекта.

Язык кода:javascript
копировать
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> 

Что следует отметить:

  1、существоватьиспользовать@Autowiredчас,Компонент соответствующего типа, который будет автоматически введен, сначала будет запрошен в контейнере IOC.

2. Если результат запроса равен единице, соберите компонент со значением атрибута, указанным @Autowired.

3. Если результатов запроса несколько, @Autowired выполнит поиск по имени атрибута.

4. Если результат запроса пуст, будет выдано исключение. Решение: используйте require=false

Тогда возникает проблема,Мы просто собираемся написать модульный тест,Зачем начинать весну? первый,запускатьSpringтолько заставит тебяrun->Junit Программа тормозит при тестировании. Это одна из причин, почему модульные тесты выполняются так медленно при каждом запуске. Тогда модульный тест проверяет только методы определенного класса. Запуск Spring совершенно избыточен, поэтому нам нужен только соответствующий экземпляр класса сущности. Когда нам нужно внедрить компонент, мы напрямую используем new следующим образом:

Язык кода:javascript
копировать
@Autowired
private HelloService helloService;

Изменить на:

private HelloService helloService = new HelloServiceImpl();

// Этот HelloServiceImpl является соответствующим классом реализации для каждого из ваших интерфейсов.

2. Не следует использовать@SpringBootTest

Язык кода:javascript
копировать
@SpringBootTest(classes = Application.class)

Этот @SpringBootTest просто преступен! Это виновник того, что модульный тест работает очень медленно при каждом запуске.,Поверьте мне,удали это свое Юнит-тест Скорость улетит быстро。@SpringBootTest, как и @Autowired, полностью избыточен в модульных тестах.,Две вещи, которые совершенно не сочетаются друг с другом! Каждый раз, когда модульный тест сначала запускает SpringBoot

Тогда давайте взглянем на исходный код @SpringBootTest.

Примерное значение:

1. @SpringBootTest используется в проекте SpringBoot. Он считывает атрибуты файла конфигурации на основе @SpringBootContextLoader.

2. Предоставьте следующие функции поверх обычной среды Spring TestContext:

1) Когда в определении нет конкретного @ContextConfiguration(loader=…),использоватьSpringBootContextLoaderкакпо умолчаниюизContextLoader。ContextLoaderизэффект:на самом деле поContextLoaderListenerвыполнение вызовакорневой контекст приложенияиз Работа по инициализации。

2) Если вложенный @Configuration не используется, автоматически выполняется поиск @SpringBootConfiguration без указания явного класса.

3) Позволяет определять пользовательские свойства среды с помощью атрибута свойств.

4. Обеспечить поддержку различных режимов веб-среды, включая запуск полностью работающего веб-сервера и прослушивание определенного или случайного порта.

5) Зарегистрируйте компонент TestRestTemplate или WebTestClient для использования полностью работающего веб-сервера в веб-тестах.

Использование

Язык кода:javascript
копировать
@SpringBootTest(classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

Сейчас обычно пишут так
@SpringBootTest(classes = Application.class)

или это
@SpringBootTest

Но как бы там ни было написано, эту аннотацию использовать не следует

классы = Application.class указывает класс запуска,При выполнении здесь,Прочитаю и проанализирую некоторые файлы конфигурации проекта.,Он также подключится к библиотеке данных.,Затем, если у класса запуска есть другие классы запуска, @Component, многопоточность и т. д.,При выполнении модульного тестирования программа не только работает медленно и занимает много времени, но также может привести к изменению данных в вашей базе данных из-за воздействия многопоточных задач, даже если вы используете аннотацию отката транзакции @Transactional.

3. Базу данных не следует называть

Язык кода:javascript
копировать
@Transactional
@Rollback(true) // Транзакции автоматически откатываются. Значение по умолчанию — true. Нет необходимости писать

Цели модульного теста,то естьПолное отсутствие в базе данных!Если эта аннотацияиспользовать,Все совершенно наоборот.,Как правило, единицей измерения, использующей эту аннотацию, является тест.,После выхода из библиотеки данных будет сообщено о многих ошибках выполнения.

4. Следует использовать Assert

Assertутверждениеиз Использование,Вы можете прочитать этот блог:Использование Assert в модульном тесте

Так как же нам писать модульные тесты?

4. Правильный способ написания модульных тестов: макет базы данных

Сначала поместите правильный пример модульного теста

Язык кода:javascript
копировать
    //@SpringBootTest
    //@SpringBootTest(classes = Application.class)
    // Этот класс также запускается при запуске класса запуска, поэтому его также необходимо ввести.
    //@Import(ApplicationStartup.class)
    // Не выполняйте в проекте методы, аннотированные компонентом.
    //@TestComponent

    // Примечание 1. Аннотация RunWith сохраняется.
    @RunWith(SpringRunner.class)
    public class HelloServiceTest {
        
        //@Autowired
        // Не используйте Autowired, не запускайте контейнер Spring и не создайте экземпляр класса реализации метода, который необходимо реализовать напрямую с помощью new.
        private HelloService helloService = new HelloServiceImpl();


        @Test
        public void sayHello() {
            // Чтобы смоделировать EntityManager JPA, необходимо смоделировать все официальные интерфейсы и классы.
            EntityManager em =  init(helloService);
            
            // Any() заменяет параметры любого типа
            Mockito.doReturn("Я — возвращаемое значение моделирования").when(em).findById( any());
            // Методы без возвращаемых значений не нужно писать отдельно, поскольку они автоматически моделируются при моделировании классов сущностей.
            Mockito.doNothing().when(em).find(any());
            
            helloService.sayHello("zhangsan");
            Assert.isTrue(true,"Совершенно правильный модульный тест");
        }


        EntityManager init(Object classInstance ){
            // класс для издевательств
            EntityManager em = Mockito.mock(EntityManager.class);
            // Укажите класс отражения
            Class<?> clazz = classInstance.getClass();
            // Получить атрибуты указанного класса
            Field field = null;
            try {
                field = clazz.getDeclaredField("em");
                // Значение true затем указывает, что отраженный объект должен быть отменен при использовании Java Проверка языкового доступа.
                // Значение false затем указывает, что отраженный объект должен реализовать Java Проверка языкового доступа.
                // по умолчанию false
                field.setAccessible(true);
                // Изменить стоимость частной собственности
                field.set(classInstance, em);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return em;
        }

    }

    // HelloServiceImpl — это класс реализации. Следующий код предназначен только для выражения смысла.
    class HelloServiceImpl {
        @Autowired
        private EntityManager et;

        sayHello(String name) {
            // Методы работы с библиотекой данных без возвращаемых значений
            et.find(name);
            // Методы с возвращаемыми значениями
            String oldSecondName = et.findById(name.substring(2));
            
          
        }
    }

Вы можете видеть, что аннотация @RunWith сохраняется.

1. @RunWith В JUnit много Runner. Они отвечают за вызов вашего тестового кода. Каждый Runner имеет свои собственные специальные функции. Вам нужно выбрать другой Runner для запуска тестового кода в соответствии с вашими потребностями. Обычно используется SpringRunner.class.

2. Если мы просто проводим обычное тестирование Java и не задействуем Spring Web-проекты, вы можете опустить аннотацию @RunWith, чтобы система автоматически использовала Runner по умолчанию для запуска вашего кода.

Тогда самое главное — Mock. Необходимые для Mock банки уже включены здесь.

Язык кода:javascript
копировать
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

На этом этапе вам потребуются базовые знания Mock. Mock предназначен для моделирования всех шагов по работе с базой данных без выполнения какого-либо SQL. Мы непосредственно моделируем код, который успешно управляет базой данных при выполнении и может имитировать любое возвращаемое значение. основные аннотации.

@MockBean

Пока он локальный и вы пишете свои собственные bean-компоненты, вы можете использовать эту аннотацию. Она будет моделировать все методы работы с базой данных. Если это метод, который не возвращает значение, мы можем его игнорировать. Если существует метод, который возвращает значение, мы можем вернуть ему значение, которое нам нужно смоделировать. Использование следующее:

Язык кода:javascript
копировать
             // Any() заменяет параметры любого типа
            Mockito.doReturn("Я — возвращаемое значение моделирования").when(em).findById( any());
            // Методы без возвращаемых значений не нужно писать отдельно, поскольку они автоматически моделируются при моделировании классов сущностей.
            Mockito.doNothing().when(em).find(any());

@SpyBean

Если мы местные и звоним в другие компании, в других местах для нас написаны интерфейсы. Вместо того, чтобы работать с собственной базой данных, мы пишем нам входные параметры, а другие возвращают нам значения, поэтому мы используем это. Его использование такое же, как @MockBean.

Основные различия в использовании между ними:

MockBean является локальным и имитирует все методы.

SpyBean подходит для различных удаленных сред и моделирует только отдельные методы.

Тогда то, что мы здесь высмеиваем, — это официальный EntityManager JPA. Чтобы официальные интерфейсы и классы использовались в качестве частных атрибутов в наших классах реализации для управления базой данных, мы можем смоделировать их с помощью этого метода.

Язык кода:javascript
копировать
    EntityManager init(Object classInstance ){
            // класс для издевательств
            EntityManager em = Mockito.mock(EntityManager.class);
            // Укажите класс отражения
            Class<?> clazz = classInstance.getClass();
            // Получить атрибуты указанного класса
            Field field = null;
            try {
                field = clazz.getDeclaredField("em");
                // Значение true затем указывает, что отраженный объект должен быть отменен при использовании Java Проверка языкового доступа.
                // Значение false затем указывает, что отраженный объект должен реализовать Java Проверка языкового доступа.
                // по умолчанию false
                field.setAccessible(true);
                // Изменить стоимость частной собственности
                field.set(classInstance, em);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return em;
        }

Если ваш проект не такой уж сложный, вам нужно всего лишь добавить аннотацию @MockBean поверх класса, который вы хотите имитировать. Обычно это используется, например.

Язык кода:javascript
копировать
    public class HelloServiceTest {
        
        //@Autowired
        // Не используйте Autowired, не запускайте контейнер Spring и не создайте экземпляр класса реализации метода, который необходимо реализовать напрямую с помощью new.
        
        private HelloService helloService = new HelloServiceImpl();

        @MockBean
        HelloDao dao;

        @Test
        public void sayHello() {
           
            // Any() заменяет параметры любого типа
            Mockito.doReturn("Я — возвращаемое значение моделирования").when(dao).findById( any());
            // Методы без возвращаемых значений не нужно писать отдельно, поскольку они автоматически моделируются при моделировании классов сущностей.
            Mockito.doNothing().when(dao).find(any());
            
            helloService.sayHello("zhangsan");
            Assert.isTrue(true,"Совершенно правильный модульный тест");
        }

Этот код может не иметь смысла по сравнению с приведенным выше. Я набрал его случайно. Я хочу выразить следующее: если вам не нужно моделировать официальные интерфейсы и классы для работы с базой данных, вы можете напрямую добавить аннотацию @MockBean или @SpyBean. поверх вашего класса реализации, а затем просто используйте синтаксис Mockito.

Если вы понимаете, о чем я?

Частичная ссылка:

Спринг Бот2.

Springboot тест – короткая книга

Подробный анализ аннотации Spring @Component_L Xiaoyun's blog-CSDN blog_@comComponent параметры

Издатель: Лидер стека программистов полного стека, укажите источник для перепечатки: https://javaforall.cn/133517.html Исходная ссылка: https://javaforall.cn

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose