Продумать тесты для метода calc

75. Продумать тесты для метода calc

Условие задачи:
Дан метод calc(a, b, operation), который выполняет одну из четырёх арифметических операций: сложение, вычитание, умножение или деление.

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

Необходимо составить набор тестов, покрывающий основные сценарии и граничные случаи.

Код:

/*
 * Выполняет одну из операций "+", "-", "/", "*"
 * для переданных параметров.
 *
 * В любой непонятной ситуации, в том числе при невозможности
 * разделить нацело, бросается exception.
 */
public Integer calc(Integer a, Integer b, String operation);

Спойлеры к решению

Подсказки
💡 Проверь каждую из четырёх поддерживаемых операций.
💡 Для деления важны отдельные случаи: без остатка, с остатком и деление на ноль.
💡 Проверь null для каждого аргумента и некорректные значения operation.
💡 Не забудь отрицательные числа и ноль.
💡 Подумай, определено ли документацией поведение при переполнении Integer.

Решение

Основные тестовые сценарии:

СценарийВызовРезультат
Сложениеcalc(2, 3, "+")5
Вычитаниеcalc(10, 4, "-")6
Умножениеcalc(6, 7, "*")42
Деление без остаткаcalc(8, 2, "/")4
Деление с остаткомcalc(7, 2, "/")exception
Деление на нольcalc(5, 0, "/")exception
Неизвестная операцияcalc(5, 2, "%")exception
operation == nullcalc(5, 2, null)exception
Пустая операцияcalc(5, 2, "")exception
a == nullcalc(null, 2, "+")exception
b == nullcalc(5, null, "+")exception
Отрицательные числаcalc(-3, -7, "*")21
Деление отрицательного числаcalc(-8, 2, "/")-4
Ноль как аргументcalc(0, 5, "+")5
Умножение на нольcalc(5, 0, "*")0

Пример на JUnit 5:

class CalcServiceTest {

    private final CalcService service = new CalcService();

    @ParameterizedTest
    @CsvSource({
            "2, 3, +, 5",
            "10, 4, -, 6",
            "6, 7, *, 42",
            "8, 2, /, 4",
            "-3, -7, *, 21",
            "-8, 2, /, -4",
            "0, 5, +, 5",
            "5, 0, *, 0"
    })
    void shouldCalculate(
            int a,
            int b,
            String operation,
            int expected
    ) {
        assertEquals(
                expected,
                service.calc(a, b, operation)
        );
    }

    @Test
    void shouldThrowWhenDivisionHasRemainder() {
        assertThrows(
                RuntimeException.class,
                () -> service.calc(7, 2, "/")
        );
    }

    @Test
    void shouldThrowWhenDividingByZero() {
        assertThrows(
                RuntimeException.class,
                () -> service.calc(5, 0, "/")
        );
    }

    @Test
    void shouldThrowForUnsupportedOperation() {
        assertThrows(
                RuntimeException.class,
                () -> service.calc(5, 2, "%")
        );
    }

    @Test
    void shouldThrowForNullArguments() {
        assertAll(
                () -> assertThrows(
                        RuntimeException.class,
                        () -> service.calc(null, 2, "+")
                ),
                () -> assertThrows(
                        RuntimeException.class,
                        () -> service.calc(2, null, "+")
                ),
                () -> assertThrows(
                        RuntimeException.class,
                        () -> service.calc(2, 2, null)
                )
        );
    }

    @Test
    void shouldThrowForEmptyOperation() {
        assertThrows(
                RuntimeException.class,
                () -> service.calc(2, 2, "")
        );
    }
}

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

Также документация ничего не говорит о переполнении:

calc(Integer.MAX_VALUE, 1, "+")

Поэтому сначала нужно определить ожидаемое поведение: стандартное переполнение int или исключение, например при использовании Math.addExact().

Отдельно стоит уточнить, считаются ли строки вроде " + " допустимой операцией. Из текущего контракта это не следует, поэтому тест нельзя однозначно определить без уточнения требований.