Разъяснение с примерами для новичков
Модульное тестирование - это часть работы каждого разработчика, который работает в компании или пишет свой собственный код. В большинстве случаев новые Java-программисты начинают учиться писать тесты JUnit, что является еще одной базой для Java-разработчиков. Однако знать только JUnit недостаточно. Моки являются важной частью модульного тестирования и широко используются многими разработчиками.
Одна из самых популярных библиотек Java для работы с mockito - это Mockito.
Я понимаю, что младшим разработчикам может быть немного сложно понять, как кодировать с помощью Mockito. Это одна из библиотек Java, требующая дополнительного изучения. Я мог бы сравнить это с изучением нового диалекта - например, у голландцев очень разные диалекты, и некоторые из них могут быть трудными для понимания даже в разных частях Нидерландов. То же самое и с Mockito - он является частью Java, но требует дополнительных усилий для его изучения.
В этой статье я объясню случаи, когда разработчику стоит использовать Mockito. Также я приведу несколько примеров кода.
Зачем нужны моки?
Концепция модульного тестирования заключается в тестировании только одного класса / модуля в тестовом классе. Однако в большинстве случаев у этого класса есть несколько других зависимостей. Например, класс обслуживания может иметь зависимости от других классов: репозиториев, помощников и т. Д. Класс обслуживания вызывает код из других классов, но мы хотим протестировать только класс this-service, а не все остальные, которые зависимы. Боковая реализация в других классах не должна интересовать нас при написании модульного теста. И чтобы избежать побочного тестирования, мы должны использовать моки. Если бы макеты не использовались для зависимостей классов, нам пришлось бы создавать реальные объекты, и такое тестирование переросло бы в интеграционные тесты, которые не являются частью модульного тестирования.
Замена зависимости и фиктивная реализация такой зависимости называется макетом.
Практические примеры
В качестве примера кода я использую Spring Boot с Maven. Первое, что нужно сделать, это добавить необходимую зависимость в файл pom:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>3.3.3</version> <scope>test</scope> </dependency>
Я собираюсь протестировать класс BookService, который имеет только один метод с несколькими зависимостями:
@Component public class BookService { private static final Logger LOGGER = Logger.getLogger("InfoLoggin"); @Autowired private BookRepo bookRepo; @Autowired private VoiceOverAgencyRepository agencyRepo; public Book saveBook(Book book) throws CustomException { //1. Checking if this book already exists in database. if (bookRepo.existsByIbanId(book.getIbanId())) { throw new CustomException(); } //2. Setting registration date of a new book. book.setRegistrationDateTime(LocalDateTime.now()); //3. Checking if this book has an audio version. //If not, we register it in agency's DB too. if (book.isAudioVersion()) { LOGGER.info(book.getBookName() + ": audio version exists."); } else { agencyRepo.registerBookForAudio(book.getIbanId()); } //4. Saving into the database. return bookRepo.save(book); } }
Этот метод получает объект книги и проверяет:
а) Если такая книга уже существует в базе данных. Если книга существует, генерируется специальное исключение.
б) Если книги еще нет в базе данных, устанавливается дата и время регистрации.
в) Проверяет, есть ли у книги аудиоверсия. Если у него нет аудиоверсии, он регистрирует эту книгу в репозитории голосового агентства.
г) Наконец, он регистрирует эту книгу в хранилище книг.
Как видите, у этого класса есть два автоматически подключенных объекта: репозиторий голосового агентства и репозиторий книг. Мы вызываем оба репозитория в этом классе, но они будут выполнять свою работу в своих собственных классах. Мы не хотим тестировать функциональность других классов, а это значит, что эти два объекта будут имитированы.
Пойдем шаг за шагом
- Мне нужно создать тестовый класс и добавить все зависимости.
a) Мой тестовый класс получает аннотацию @ExtendWith (MockitoExtensionc.class).
Эта аннотация содержит все макеты. в тестовом классе.
б) Я издеваюсь над всеми зависимостями с помощью аннотации @Mock и вставляю эти макеты в сервисный класс, который я хочу протестировать, используя аннотацию @InjectMocks :
@ExtendWith(MockitoExtension.class) public class TestBookService { @Mock private Book book; @Mock private BookRepo bookRepo; @Mock private VoiceOverAgencyRepository agencyRepo; @InjectMocks private BookService bookService; ...
Как видите, я высмеял все зависимости, которые использовались с аннотацией @Autowired в классе BookService. Я также издевался над книгой, которую использую в качестве параметра для своего метода в классе BookService. Я смогу манипулировать этими объектами, как захочу, и мне не нужно будет создавать настоящие объекты.
2. Вернемся к классу BookService.
Здесь есть только один метод, и я буду тестировать его шаг за шагом. Это означает, что я хочу создать несколько разных методов тестирования, которые будут проверять различные действия моего метода.
Первая пара строк моего метода проверяет, находится ли книга уже в репозитории книг. И если он существует, он генерирует настраиваемое исключение:
public Book saveBook(Book book) throws CustomException { //1. Checking if this book already exists in database. if (bookRepo.existsByIbanId(book.getIbanId())) { throw new CustomException(); }
Как я могу это проверить? Я должен сказать своему тест-классу, что эта книга уже существует в репозитории, поэтому она вызовет исключение. Я могу сделать это с помощью методов Mockito , когда…. thenReturn… Я передаю переменную Mockito any (), потому что она не обязательно должна быть реальным объектом - единственное, что меня волнует, - это вызов моего исключения. Поэтому я говорю Mockito: когда вы дойдете до строки кода, в которой написано bookRepo.existsByIbanId (any ()), затем верните true . Теперь я могу проверить, действительно ли моя программа выдает исключение. Проверить это:
3. Вот вторая часть класса обслуживания:
//2. Setting registration date of a new book. book.setRegistrationDateTime(LocalDateTime.now());
Мой тест должен проверить, действительно ли класс обслуживания выполняет эту строку. Я хочу быть уверенным, что мой код где-то не закончился, не дойдя до него.
Для этого я могу использовать метод Mockito verify (что что-то произошло).
Перед вызовом этого метода я должен дать тестовый класс инструкции:
а) когда мой код достигает 1-й строки, которая проверяет, существует ли уже эта книга, я должен сообщить своему коду, что эта (фиктивная) книга еще не существует (иначе мой код вызовет исключение).
б) после этого мой код дойдет до строки, которую я хочу протестировать. Однако этого недостаточно. Я должен написать инструкции для моего метода тестирования и что он должен делать, когда достигает следующих строк метода:
4. Перейдем к следующей части метода:
//3. Checking if this book has an audio version. // If not, we register it in agency's DB too. if (book.isAudioVersion()) { LOGGER.info(book.getBookName() + ": audio version exists."); } else { agencyRepo.registerBookForAudio(book.getIbanId()); }
Я хочу быть уверенным, что мой код действительно регистрирует книгу в репозитории агентства, если у нее нет аудиоверсии. В этом случае я скажу своему макету, что у книги нет аудиоверсии.
5. И наоборот: если аудиоверсия уже существует, я хочу быть уверенным, что мой код никогда не регистрирует эту книгу для агентства. Я могу сказать своей функции verify, что эту строку никогда не следует вызывать:
6. Наконец, последняя часть класса BookService:
//4. Saving into the database. return bookRepo.save(book); } }
Я хочу быть уверен, что он сохранит правильный объект в базе данных.
Для этого я создам реальный объект книги и сохраню данные, которые я сохраняю в хранилище книг.
ArgumentCaptor разрешает доступ к аргументам вызовов методов и проверяет их. Он работает вместе с методом verify (..). Я легко могу это инициировать:
ArgumentCaptor ‹ObjectThatIWantToCapture› captor = ArgumentCaptor.forClass (ObjectThatIWantToCapture.class);
Давайте посмотрим:
Теперь вы узнали, как писать модульные тесты с использованием Mockito. Мои примеры включают наиболее часто используемые методы Mockito, которые вы можете использовать для своего кода:
- when (будет вызван какой-то метод) .thenReturn (something);
- проверить (что этот метод будет вызываться, x раз);
- проверить (никогда не будет вызван этот метод);
- проверить (что при вызове этого метода)… (захват фиксирует аргументы, которые я хочу проверить).
Освоив эти базовые функции Mockito, вы сможете глубже погрузиться в них и обеспечить лучшее качество своего кода.
Более подробную информацию о Mockito можно найти на официальном сайте документации.