← Назад к вопросам

Делал ли граничные тесты

1.7 Middle🔥 161 комментариев
#Тестирование

Комментарии (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.

Делал ли граничные тесты | PrepBro