Софтуерно тестване с JUnit - тестване на кода
Видове Тестове в Софтуерното Инженерство
- Unit Testing: Тества най-малките части на приложението независимо (например, функции или методи).
- Integration Testing: Проверява взаимодействията между различни модули или външни системи.
- Functional Testing: Фокусира се върху бизнес изискванията и функционалността на софтуера.
- End-to-End Testing: Симулира потребителски сценарии и тества целия процес от начало до край.
- Performance Testing: Измерва производителността на приложението при различни условия.
- Security Testing: Проверява за уязвимости в софтуера и сигурността на данните.
- UI Testing: Тества потребителския интерфейс на приложението.
Unit Testing в Java с JUnit
Unit тестовете са основен инструмент за осигуряване на качеството на софтуера. Те позволяват на разработчиците да проверяват функционалността на отделни компоненти в изолация. В Java, най-използваната библиотека за unit тестове е JUnit.
Защо са Важни Unit Тестовете
- Ранно Откриване на Грешки: Помагат за откриване на проблеми в кода в ранен стадий.
- Документация: Предоставят практически примери за начина на използване на кода.
- Поддръжка и Рефакторинг: Улесняват рефакторинга на кода, като осигуряват увереност, че промените не нарушават съществуващата функционалност.
JUnit: Основни Концепции и Употреба
Unit е водеща рамка в Java за разработване на unit тестове. Тя предоставя прост и интуитивен интерфейс за написване и изпълнение на тестове. JUnit е изключително важна за обезпечаване на качеството на софтуера, като позволява на разработчиците да тестват своя код преди да го интегрират в по-голямата система.
Компоненти на JUnit JUnit включва различни компоненти, които са важни за писането на ефективни тестове:
1 Анотации: JUnit предоставя редица анотации, които помагат да се определи поведението на тестовете.
2 Assert Методи: Тези методи са използвани за проверка на условия в кода, които трябва да бъдат изпълнени.
3 Тестови Класове: Тестовите класове съдържат тестови методи, които се изпълняват от JUnit.
4 Тестови Сюити: Тестовите сюити са групи от тестови класове, които се изпълняват заедно.
Анотации в JUnit
-
@Test:
Анотацията
@Test
дефинира метод като тестов случай. Това е основният начин за маркиране на метод, който JUnit трябва да изпълни като те ст.@Test
public void testAddition() {
...
} -
@BeforeEach / @Before:
Изпълнява се преди всяко изпълнение на тестов метод в даден тестов клас.
@BeforeEach
public void setup() {
System.out.println("Подготовка преди всеки тест");
} -
@AfterEach / @After:
Изпълнява се след всеки тестов метод. Обикновено се използва за почистване на ресурси.
@AfterEach
public void tearDown() {
System.out.println("Почистване след всеки тест");
} -
@BeforeClass / @BeforeAll:
Изпълнява код преди стартирането на всички тестове в класа.
@BeforeClass
public static void setup() {
System.out.println("Подготовка преди всички тестове");
} -
@AfterClass / @AfterAll:
Изпълнява код след приключване на всички тестове в класа.
@AfterClass
public static void tearDown() {
System.out.println("Почистване след всички тестове");
} -
@DisplayName:
Анотацията
@DisplayName
се използва за задаване на четимо име на тестовия метод. Това име се използва в резултатите от изпълнението на теста.@Test
@DisplayName("Тест за събиране")
public void testAddition() {
...
} -
@Disabled:
Анотацията
@Disabled
се използва за временно изключване на тестов метод. Този метод няма да се изпълни при следващото изпълнение на тестовия клас.@Test
@Disabled("Този тест е временно изключен")
public void testAddition() {
...
} -
@Timeout:
Анотацията
@Timeout
се използва за задаване на време за изпълнение на тестовия метод. Ако тестът не приключи в рамките на зададеното време, той ще бъде прекъснат.@Test
@Timeout(5)
public void testAddition() {
...
} -
@Nested:
Анотацията
@Nested
се използва за дефиниране на вложени тестови класове. Това е полезно, когато има няколко тестови клас а, които са тясно свързани помежду си.@Nested
class AdditionTests {
@Test
public void testAddition() {
...
}
} -
@Tag:
Анотацията
@Tag
се използва за маркиране на тестови методи с етикети. Това е полезно, когато искаме да изпълним само определени тестове.@Test
@Tag("fast")
public void testAddition() {
...
} -
@RepeatedTest:
Анотацията
@RepeatedTest
се използва за повтаряне на тестов метод няколко пъти. Това е полезно, когато искаме да изпълним тестове с различни входни данни.@RepeatedTest(5)
public void testAddition() {
...
} -
@ParameterizedTest:
Анотацията
@ParameterizedTest
се използва за изпълнение на тестов метод с различни входни данни. Това е полезно, когато искаме да изпълним тестове с различни входни данни.@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
public void testAddition(int number) {
...
} -
@CsvSource:
Анотацията
@CsvSource
се използва за изпълнение на тестов метод с входни данни от CSV файл. Това е полезно, когато искаме да изпълним тестове с различни входни данни.@ParameterizedTest
@CsvSource({"1, 2, 3", "4, 5, 9"})
public void testAddition(int a, int b, int expected) {
...
} -
@CsvFileSource:
Анотацията
@CsvFileSource
се използва за изпълнение на тестов метод с входни данни от CSV файл. Това е полезно, когато искаме да изпълним тестове с различни входни данни.@ParameterizedTest
@CsvFileSource(resources = "/data.csv")
public void testAddition(int a, int b, int expected) {
...
} -
@MethodSource:
Анотацията
@MethodSource
се използва за изпълнение на тестов метод с входни данни от метод. Това е полезно, когато искаме да изпълним тестове с различни входни данни.@ParameterizedTest
@MethodSource("dataProvider")
public void testAddition(int a, int b, int expected) {
...
}
static Stream<Arguments> dataProvider() {
return Stream.of(
Arguments.of(1, 2, 3),
Arguments.of(4, 5, 9)
);
} -
@ExtendWith:
Анотацията
@ExtendWith
се използва за добавяне на разширения към JUnit. Това е полезно, когато искаме да добавим допълнителна функционалност към JUnit.@ExtendWith(MyExtension.class)
class MyTest {
...
} -
@RegisterExtension:
Анотацията
@RegisterExtension
се използва за регистриране на разширения към JUnit. Това е полезно, когато искаме да добавим допълнителна функционалност към JUnit.@RegisterExtension
static MyExtension myExtension = new MyExtension();
@Test
public void testAddition() {
...
} -
@TempDir:
Анота цията
@TempDir
се използва за създаване на временна директория за изпълнение на тестов метод. Това е полезно, когато искаме да изпълним тестове, които изискват временни файлове.@Test
public void testAddition(@TempDir Path tempDir) {
...
} -
@TempFile:
Анотацията
@TempFile
се използва за създаване на временен файл за изпълнение на тестов метод. Това е полезно, когато искаме да изпълним тестове, които изискват временни файлове.@Test
public void testAddition(@TempFile Path tempFile) {
...
} -
@DisplayNameGeneration:
Анотацията
@DisplayNameGeneration
се използва за задаване на генератор на имена на тестови методи. Това е полезно, когато искаме да изпълним тестове с четими имена.@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class MyTest {
...
} -
@DisplayNameGenerator:
Анотацията
@DisplayNameGenerator
се използва за задаване на генератор на имена на тестови методи. Това е полезно, когато искаме да изпълним тестове с четими имена.@DisplayNameGenerator(DisplayNameGenerator.ReplaceUnderscores.class)
class MyTest {
...
}
Assert Методи
-
assertEquals(expected, actual)
:Проверява дали две стойности са равни.
assertEquals(5, calculator.add(2, 3));
-
assertNotEquals(expected, actual)
:Проверява дали две стойности не са равни.
assertNotEquals(5, calculator.add(2, 3));
-
assertSame(expected, actual)
:Проверява дали две стойности са една и съща.
assertSame(5, calculator.add(2, 3));
-
assertNotSame(expected, actual)
:Проверява дали две стойности не са една и съща.
assertNotSame(5, calculator.add(2, 3));
-
assertArrayEquals(expected, actual)
:Проверява дали два масива са еднакви.
assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3});
-
assertIterableEquals(expected, actual)
:Проверява дали два итератора са еднакви.
assertIterableEquals(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3));
-
assertLinesMatch(expected, actual)
:Проверява дали два списъка съдържат едни и същи елементи.
assertLinesMatch(Arrays.asList("1", "2", "3"), Arrays.asList("1", "2", "3"));
-
assertAll(executables)
:Проверява дали всички изпълними кодове не хвърлят изключение.
assertAll(
() -> assertEquals(5, calculator.add(2, 3)),
() -> assertEquals(1, calculator.subtract(3, 2))
); -
assertThrows(exception, executable)
:Проверява дали изпълнимият код хвърля изключение.
assertThrows(IllegalArgumentException.class, () -> calculator.add(null, 3));
-
assertTimeout(timeout, executable)
:Проверява дали изпълнимият код приключва в рамките на определено време.
assertTimeout(Duration.ofMillis(100), () -> calculator.add(2, 3));
-
fail(message)
:Приключва теста с грешка.
fail("Тестът е провален");
Примерен Тестов Клас
Този тестов клас демонстрира всички анотации и assert методи в JUnit.
import org.junit.jupiter.api.*;
import java.time.Duration;
import java.util.Arrays;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("Примерен Тестов Клас")
class SampleTest {
private Calculator calculator;
@BeforeAll
public static void setup() {
System.out.println("Подготовка преди всички тестове");
}
@BeforeEach
public void init() {
System.out.println("Подготовка преди всеки тест");
calculator = new Calculator();
}
@Test
@DisplayName("Тест за събиране")
public void testAddition() {
assertEquals(5, calculator.add(2, 3));
}
@Test
@DisplayName("Тест за деление с нула")
public void testDivisionByZero() {
assertThrows(IllegalArgumentException.class, () -> calculator.divide(6, 0));
}
@Test
@DisplayName("Тест за събиране с време за изпълнение")
public void testAdditionWithTimeout() {
assertTimeout(Duration.ofMillis(100), () -> calculator.add(2, 3));
}
@Test
@DisplayName("Тест за събиране с всички проверки")
public void testAdditionWithAllAssertions() {
assertAll(
() -> assertEquals(5, calculator.add(2, 3)),
() -> assertEquals(1, calculator.subtract(3, 2))
);
}
@Test
@DisplayName("Тест за събиране с грешка")
public void testAdditionWithFail() {
fail("Тестът е провален");
}
@Nested
class AdditionTests {
@Test
public void testAddition() {
assertEquals(5, calculator.add(2, 3));
}
}
@RepeatedTest(5)
public void testAdditionRepeated() {
assertEquals(5, calculator.add(2, 3));
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
public void testAddition(int number) {
assertEquals(5, calculator.add(2, 3));
}
@ParameterizedTest
@CsvSource({"1, 2, 3", "4, 5, 9"})
public void testAddition(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
@ParameterizedTest
@CsvFileSource(resources = "/data.csv")
public void testAddition(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
@ParameterizedTest
@MethodSource("dataProvider")
public void testAddition(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
static Stream<Arguments> dataProvider() {
return Stream.of(
Arguments.of(1, 2, 3),
Arguments.of(4, 5, 9)
);
}
@Test
@Disabled("Този тест е временно изключен")
public void testAdditionDisabled() {
assertEquals(5, calculator.add(2, 3));
}
@Test
@Timeout(5)
public void testAdditionWithTimeout() {
assertEquals(5, calculator.add(2, 3));
}
@AfterEach
public void tearDown() {
System.out.println("Почистване след всеки тест");
}
@AfterAll
public static void done() {
System.out.println("Почистване след всички тестове");
}
}
Примерен Тестов Сюит
Този тестов сюит демонстрира как да се изпълнят тестови класове с JUnit.
import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.SuiteDisplayName;
import org.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
@SuiteDisplayName("Примерен Тестов Сюит")
@SelectPackages("com.example")
@SelectClasses({SampleTest.class, AnotherTest.class})
class SampleTestSuite {
}
Напреднали Техники в JUnit
-
Mocking:
Позволява създаването на измамни обекти, които се използват за симулиране на поведението на реални обекти.
Mocking с Mockito
Mockito е популярна библиотека за създаване на mock обекти в Java. Тя позволява симулирането на поведение и взаимодействие с обекти без необходимостта от реалната им имплементация.
@Test
public void testAddition() {
Calculator calculator = mock(Calculator.class);
when(calculator.add(2, 3)).thenReturn(5);
assertEquals(5, calculator.add(2, 3));
}
Заключение
Unit тестовете са жизненоважни за създаването на надежден и поддържаем софтуер. JUnit предлага мощен набор от инструменти за писане и изпълнение на тестове в Java, което улеснява контрола на качеството на кода. Правилното използване на JUnit и свързаните с него практики води до по-чист, по-надежден и по-добре структуриран код.