Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Граничные Тесты (Boundary Testing) в Java
Определение
Граничные тесты — это техника тестирования, проверяющая поведение приложения на граничных (edge case) значениях диапазонов. Обычно это:
- Минимальное значение - 1
- Минимальное значение
- Максимальное значение
- Максимальное значение + 1
Почему Это Важно?
Ошибки часто возникают на границах:
- Off-by-one ошибки
- Integer overflow
- Array index out of bounds
- Null pointer exceptions
- Бесконечные циклы
Пример 1: Числовые Границы
public class AgeValidator {
public boolean isValidAge(int age) {
return age >= 18 && age <= 100;
}
}
// Граничные тесты
public class AgeValidatorTest {
private AgeValidator validator = new AgeValidator();
@Test
public void testBoundaryValues() {
// Ниже минимума
assertFalse(validator.isValidAge(17)); // Граница: 18 - 1
// Минимум
assertTrue(validator.isValidAge(18)); // Граница: 18
// Максимум
assertTrue(validator.isValidAge(100)); // Граница: 100
// Выше максимума
assertFalse(validator.isValidAge(101)); // Граница: 100 + 1
}
@Test
public void testInvalidBoundaries() {
assertFalse(validator.isValidAge(-1));
assertFalse(validator.isValidAge(0));
assertFalse(validator.isValidAge(101));
assertFalse(validator.isValidAge(Integer.MAX_VALUE));
}
}
Пример 2: String Length Validation
public class PasswordValidator {
public boolean isValidPassword(String password) {
// Требование: от 8 до 20 символов
if (password == null) return false;
return password.length() >= 8 && password.length() <= 20;
}
}
// Граничные тесты
public class PasswordValidatorTest {
private PasswordValidator validator = new PasswordValidator();
@Test
public void testPasswordLengthBoundaries() {
// Ниже минимума
assertFalse(validator.isValidPassword("1234567")); // 7 символов
// Минимум
assertTrue(validator.isValidPassword("12345678")); // 8 символов
// Максимум
assertTrue(validator.isValidPassword("12345678901234567890")); // 20 символов
// Выше максимума
assertFalse(validator.isValidPassword("123456789012345678901")); // 21 символ
}
@Test
public void testPasswordNullAndEmpty() {
assertFalse(validator.isValidPassword(null));
assertFalse(validator.isValidPassword(""));
}
}
Пример 3: Array Index Boundaries
public class ArrayProcessor {
public int getElement(int[] array, int index) {
if (array == null || index < 0 || index >= array.length) {
throw new IndexOutOfBoundsException("Invalid index: " + index);
}
return array[index];
}
public void setElement(int[] array, int index, int value) {
if (array == null) {
throw new NullPointerException("Array cannot be null");
}
if (index < 0 || index >= array.length) {
throw new IndexOutOfBoundsException("Invalid index: " + index);
}
array[index] = value;
}
}
// Граничные тесты
public class ArrayProcessorTest {
private ArrayProcessor processor = new ArrayProcessor();
private int[] testArray = {10, 20, 30, 40, 50};
@Test
public void testGetElementBoundaries() {
// Первый элемент
assertEquals(10, processor.getElement(testArray, 0));
// Последний элемент
assertEquals(50, processor.getElement(testArray, 4));
// Ниже границы
assertThrows(IndexOutOfBoundsException.class,
() -> processor.getElement(testArray, -1));
// Выше границы
assertThrows(IndexOutOfBoundsException.class,
() -> processor.getElement(testArray, 5));
}
@Test
public void testSetElementBoundaries() {
processor.setElement(testArray, 0, 100);
assertEquals(100, testArray[0]);
processor.setElement(testArray, 4, 500);
assertEquals(500, testArray[4]);
assertThrows(IndexOutOfBoundsException.class,
() -> processor.setElement(testArray, -1, 999));
assertThrows(IndexOutOfBoundsException.class,
() -> processor.setElement(testArray, 5, 999));
}
@Test
public void testNullArray() {
assertThrows(NullPointerException.class,
() -> processor.getElement(null, 0));
}
}
Пример 4: Range-Based Operations
public class ScoreCalculator {
public String getGrade(int score) {
// A: 90-100, B: 80-89, C: 70-79, D: 60-69, F: 0-59
if (score < 0 || score > 100) {
throw new IllegalArgumentException("Score must be 0-100");
}
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}
}
// Комплексные граничные тесты
public class ScoreCalculatorTest {
private ScoreCalculator calculator = new ScoreCalculator();
@ParameterizedTest
@ValueSource(ints = {89, 90, 91})
public void testABoundary(int score) {
assertEquals("A", calculator.getGrade(90));
assertEquals("B", calculator.getGrade(89));
}
@ParameterizedTest
@ValueSource(ints = {79, 80, 81})
public void testBCBoundary(int score) {
assertEquals("C", calculator.getGrade(79));
assertEquals("B", calculator.getGrade(80));
}
@Test
public void testAllGradeBoundaries() {
// F-D граница
assertEquals("F", calculator.getGrade(59));
assertEquals("D", calculator.getGrade(60));
// D-C граница
assertEquals("D", calculator.getGrade(69));
assertEquals("C", calculator.getGrade(70));
// C-B граница
assertEquals("C", calculator.getGrade(79));
assertEquals("B", calculator.getGrade(80));
// B-A граница
assertEquals("B", calculator.getGrade(89));
assertEquals("A", calculator.getGrade(90));
// Крайности
assertEquals("F", calculator.getGrade(0));
assertEquals("A", calculator.getGrade(100));
}
@Test
public void testInvalidScores() {
assertThrows(IllegalArgumentException.class,
() -> calculator.getGrade(-1));
assertThrows(IllegalArgumentException.class,
() -> calculator.getGrade(101));
}
}
Пример 5: Integer Overflow (Реальная Ошибка)
public class Calculator {
public int add(int a, int b) {
// ОШИБКА: Integer.MAX_VALUE + 1 вызовет overflow!
return a + b;
}
public int safeAdd(int a, int b) {
// Правильно: проверяем overflow
if (a > 0 && b > 0 && a > Integer.MAX_VALUE - b) {
throw new ArithmeticException("Integer overflow");
}
if (a < 0 && b < 0 && a < Integer.MIN_VALUE - b) {
throw new ArithmeticException("Integer underflow");
}
return a + b;
}
}
// Тесты
public class CalculatorTest {
private Calculator calculator = new Calculator();
@Test
public void testIntegerOverflow() {
// Граничный тест: MAX_VALUE
assertEquals(Integer.MAX_VALUE, calculator.safeAdd(Integer.MAX_VALUE, 0));
// Выше границы: overflow
assertThrows(ArithmeticException.class,
() -> calculator.safeAdd(Integer.MAX_VALUE, 1));
assertThrows(ArithmeticException.class,
() -> calculator.safeAdd(Integer.MAX_VALUE, Integer.MAX_VALUE));
}
@Test
public void testIntegerUnderflow() {
assertEquals(Integer.MIN_VALUE, calculator.safeAdd(Integer.MIN_VALUE, 0));
assertThrows(ArithmeticException.class,
() -> calculator.safeAdd(Integer.MIN_VALUE, -1));
}
}
Пример 6: Collections Boundaries
public class ListProcessor {
public <T> T getFirst(List<T> list) {
if (list == null || list.isEmpty()) {
throw new NoSuchElementException("List is empty");
}
return list.get(0);
}
public <T> T getLast(List<T> list) {
if (list == null || list.isEmpty()) {
throw new NoSuchElementException("List is empty");
}
return list.get(list.size() - 1);
}
}
// Граничные тесты
public class ListProcessorTest {
private ListProcessor processor = new ListProcessor();
@Test
public void testEmptyList() {
List<Integer> emptyList = new ArrayList<>();
assertThrows(NoSuchElementException.class,
() -> processor.getFirst(emptyList));
assertThrows(NoSuchElementException.class,
() -> processor.getLast(emptyList));
}
@Test
public void testSingleElement() {
List<Integer> singleList = Arrays.asList(42);
assertEquals(42, processor.getFirst(singleList));
assertEquals(42, processor.getLast(singleList)); // Одно и то же
}
@Test
public void testMultipleElements() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
assertEquals(1, processor.getFirst(list));
assertEquals(5, processor.getLast(list));
}
@Test
public void testNullList() {
assertThrows(NoSuchElementException.class,
() -> processor.getFirst(null));
}
}
Пример 7: Parametrized Boundary Tests
@DisplayName("Граничные тесты с параметризацией")
public class BoundaryParametrizedTest {
private AgeValidator validator = new AgeValidator();
@ParameterizedTest
@ValueSource(ints = {17, 18, 100, 101})
@DisplayName("Тестирование границ возраста")
public void testAgeBoundaries(int age) {
boolean expected = age >= 18 && age <= 100;
assertEquals(expected, validator.isValidAge(age));
}
@ParameterizedTest
@CsvSource({
"17, false",
"18, true",
"50, true",
"100, true",
"101, false"
})
@DisplayName("CSV параметрезованные граничные тесты")
public void testAgeWithCsv(int age, boolean expected) {
assertEquals(expected, validator.isValidAge(age));
}
}
Чеклист Граничных Тестов
Когда пишешь функцию, всегда проверяй:
- Минимальное значение
- Минимальное значение - 1
- Максимальное значение
- Максимальное значение + 1
- Null значения
- Пустые коллекции
- Пустые строки
- Одиночный элемент в коллекции
- Отрицательные числа (если применимо)
- Zero значение
- Integer overflow (для больших чисел)
Инструменты и Фреймворки
// JUnit 5 с параметризацией
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void testWithIntSource(int value) {}
@ParameterizedTest
@CsvSource({"1, true", "2, false"})
void testWithCsvSource(int value, boolean expected) {}
// Hypothesis для Java (property-based testing)
// Автоматически генерирует граничные значения
Заключение
Граничные тесты — это crítico аспект качественного тестирования, который предотвращает off-by-one ошибки и неожиданное поведение на краях диапазонов. Профессиональные разработчики всегда включают их в test suite.