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 == null | calc(5, 2, null) | exception |
| Пустая операция | calc(5, 2, "") | exception |
a == null | calc(null, 2, "+") | exception |
b == null | calc(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().
Отдельно стоит уточнить, считаются ли строки вроде " + " допустимой операцией. Из текущего контракта это не следует, поэтому тест нельзя однозначно определить без уточнения требований.