Модульное тестирование — это фундаментальная практика разработки программного обеспечения, обеспечивающая надежность и корректность вашего кода. В этой статье мы рассмотрим ключевые концепции модульного тестирования 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
и @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 — популярная библиотека 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 — популярный инструмент сборки проектов Java, и вы можете интегрировать анализ покрытия кода в свой проект Maven с помощью таких плагинов, как JaCoCo. Вот как добавить JaCoCo в ваш проект:
pom.xml
вашего проекта. <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.
В следующей части этой серии мы рассмотрим интеграционное тестирование — важнейший аспект тестирования программного обеспечения, который включает в себя тестирование взаимодействия между различными компонентами и службами для обеспечения правильного функционирования всей системы.
Оставайтесь с нами, чтобы увидеть вторую часть, где мы окунемся в захватывающий мир интеграционного тестирования!