paint-brush
Тестирование на Java: ключевые понятия [Часть 1: Модульное тестирование]к@gromspys
1,621 чтения
1,621 чтения

Тестирование на Java: ключевые понятия [Часть 1: Модульное тестирование]

к Sergei Korneev10m2023/11/14
Read on Terminal Reader

Слишком долго; Читать

В этой статье мы рассмотрим ключевые концепции модульного тестирования Java, включая простые примеры кода, параметризацию, тестирование исключений и аннотации.
featured image - Тестирование на Java: ключевые понятия [Часть 1: Модульное тестирование]
Sergei Korneev HackerNoon profile picture

Модульное тестирование — это фундаментальная практика разработки программного обеспечения, обеспечивающая надежность и корректность вашего кода. В этой статье мы рассмотрим ключевые концепции модульного тестирования Java, включая простые примеры кода, параметризацию, тестирование исключений, такие аннотации, как @Before , @BeforeEach , @After и @AfterEach , а также использование макетов и заглушек. .


Мы также представим популярную библиотеку Mockito для макетирования и предоставим рекомендации по измерению покрытия кода с помощью плагинов Maven.

Важность модульного тестирования

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


Давайте начнем с создания простого класса калькулятора, который мы сможем использовать в наших примерах. Класс Calculator содержит основные арифметические операции:

 public class Calculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } public int divide(int a, int b) { return a / b; } }

Написание простого модульного теста

Чтобы создать модульный тест для нашего класса Calculator , вы можете использовать среду JUnit, популярную среду тестирования для Java. Давайте напишем простой модульный тест для метода add :

 import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { @Test public void testAdd() { Calculator calculator = new Calculator(); int result = calculator.add(2, 3); assertEquals(5, result); } }

В этом тесте мы импортируем необходимые классы JUnit и аннотируем метод теста с помощью @Test . Затем мы создаем экземпляр класса Calculator и утверждаем, что результат метода add равен ожидаемому значению (5).

Параметризованные тесты

Параметризованные тесты позволяют запускать одну и ту же логику тестирования с несколькими наборами входных данных. Это полезно для тестирования метода с различными входными значениями. Чтобы сделать это в JUnit, вы можете использовать аннотацию @ParameterizedTest и предоставить входные значения и ожидаемые результаты в качестве параметров. Вот пример:

 import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { @ParameterizedTest @CsvSource({"2, 3, 5", "4, 7, 11", "0, 0, 0"}) public void testAdd(int a, int b, int expected) { Calculator calculator = new Calculator(); int result = calculator.add(a, b); assertEquals(expected, result); } }

В этом примере аннотация @ParameterizedTest позволяет нам предоставить несколько наборов входных значений и ожидаемых результатов в формате CSV. Метод проверки выполняется для каждой комбинации, обеспечивая корректность метода add .

Тестирование исключений

Тестирование исключений имеет решающее значение для обеспечения правильной обработки ошибок в коде. Чтобы проверить случаи исключений, вы можете использовать аннотацию @Test вместе с методом assertThrows , предоставляемым JUnit.


Вот пример:

 javaCopy codeimport org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertThrows; public class CalculatorTest { @Test public void testDivideByZero() { Calculator calculator = new Calculator(); assertThrows(ArithmeticException.class, () -> calculator.divide(5, 0)); } }

В этом тесте мы проверяем, что деление числа на ноль в классе Calculator вызывает ArithmeticException .

Использование аннотаций: @Before, @BeforeEach, @After и @AfterEach.

Такие аннотации, как @Before , @BeforeEach , @After и @AfterEach , используются для настройки и отключения тестовой среды. Эти аннотации помогают управлять общими задачами инициализации и очистки тестов.


  • @Before и @After запускаются один раз до и после всех тестовых методов в тестовом классе соответственно.
  • @BeforeEach и @AfterEach выполняются до и после каждого метода тестирования.


Вот пример:

 import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { private Calculator calculator; @BeforeEach public void setUp() { calculator = new Calculator(); } @AfterEach public void tearDown() { calculator = null; } @Test public void testAdd() { int result = calculator.add(2, 3); assertEquals(5, result); } @Test public void testSubtract() { int result = calculator.subtract(5, 3); assertEquals(2, result); } }

В этом примере метод setUp помечен @BeforeEach и инициализирует объект Calculator перед каждым тестом. Метод tearDown , помеченный @AfterEach , очищает ресурсы после каждого теста.

Моки и заглушки

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


  • Мок: Мок — это объект, который имитирует поведение реального объекта, но находится под вашим контролем. Он записывает взаимодействия и позволяет проверить, вызываются ли определенные методы.


  • Заглушка: заглушка предоставляет готовые ответы на вызовы методов. Он возвращает предопределенные данные и используется для моделирования определенного поведения внешней системы.


Давайте расширим класс Calculator , включив в него метод, включающий внешнюю зависимость, а затем создадим заглушку для имитации этой зависимости.

 public class Calculator { private ExternalService externalService; public Calculator(ExternalService externalService) { this.externalService = externalService; } public int performComplexCalculation(int a, int b) { int result = externalService.multiply(a, b); return result + externalService.getConstant(); } }

В этом обновленном классе Calculator performComplexCalculation использует ExternalService для умножения и получения постоянного значения.


Вот как вы можете создать заглушку для тестирования класса Calculator без зависимости от фактического ExternalService .

 import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; public class CalculatorTest { @Test public void testPerformComplexCalculation() { // Create a stub for ExternalService ExternalService externalServiceStub = new ExternalService() { @Override public int multiply(int a, int b) { return a * b; } @Override public int getConstant() { return 5; // Stubbed constant value } }; // Create the Calculator with the stubbed ExternalService Calculator calculator = new Calculator(externalServiceStub); // Test the performComplexCalculation method int result = calculator.performComplexCalculation(2, 3); assertEquals(11, result); // 2*3 + 5 (constant) = 11 } }

В этом тесте ExternalService встраивается в тестовый метод путем создания анонимного класса, который переопределяет необходимые методы. Таким образом, метод тестирования Calculator выполняется независимо от фактической реализации ExternalService .


Заглушки полезны для моделирования поведения внешних систем или зависимостей, чтобы изолировать и протестировать конкретную функциональность класса или метода. Это позволяет вам контролировать поведение заглушки и сосредоточиться на тестируемом устройстве без необходимости использования реальных внешних сервисов.

Представляем Mockito для насмешек

Mockito — популярная библиотека Java для создания и управления фиктивными объектами. Допустим, у нас есть класс PaymentService , который взаимодействует с внешним платежным шлюзом. Мы можем использовать Mockito для создания макета платежного шлюза:

 import org.junit.jupiter.api.Test; import static org.mockito.Mockito.*; public class PaymentServiceTest { @Test public void testProcessPayment() { PaymentGateway paymentGateway = mock(PaymentGateway.class); PaymentService paymentService = new PaymentService(paymentGateway); when(paymentGateway.charge(100.0)).thenReturn(true); boolean result = paymentService.processPayment(100.0); assertTrue(result); verify(paymentGateway, times(1)).charge(100.0); } }

В этом примере мы создаем макет PaymentGateway и используем метод when для определения его поведения. Затем мы вызываем метод processPayment класса PaymentService и проверяем, что метод charge был вызван с ожидаемыми параметрами.

Покрытие кода и плагин Maven

Покрытие кода измеряет процент строк кода, ветвей или операторов, которые выполняются вашими модульными тестами. Это помогает вам выявить непроверенный код и области, которые могут потребовать дополнительного тестирования.


Maven — популярный инструмент сборки проектов Java, и вы можете интегрировать анализ покрытия кода в свой проект Maven с помощью таких плагинов, как JaCoCo. Вот как добавить JaCoCo в ваш проект:


  1. Откройте файл pom.xml вашего проекта.
  2. Добавьте конфигурацию плагина JaCoCo Maven:
 <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.7</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> </plugins> </build>


Запустите сборку Maven с помощью следующей команды:

 mvn clean verify


После запуска сборки вы можете найти отчет о покрытии кода в файле target/site/jacoco/index.html . Этот отчет предоставляет подробную информацию о покрытии вашего кода модульными тестами.

Заключение

Модульное тестирование Java — важная практика для обеспечения надежности и правильности вашего кода. С помощью таких инструментов, как JUnit и Mockito, вы можете писать эффективные модульные тесты и макеты для своих компонентов.


Интегрируя анализ покрытия кода с Maven и JaCoCo, вы можете быть уверены, что ваши тесты охватывают значительную часть вашей кодовой базы. Регулярное тестирование и поддержка модульных тестов помогут вам создавать высококачественные и надежные приложения Java.


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


Оставайтесь с нами, чтобы увидеть вторую часть, где мы окунемся в захватывающий мир интеграционного тестирования!