#java #юнит_тесты #junit
Я вижу в этом паттерне только один плюс: он улучшает читаемость.
А минусов больше:
Во-первых, это далеко не всегда улучшает читаемость. Вот простой тест:
@Test
public void testAdditionAndSize() {
int expectedSize = 3;
int actualSize = list.size();
assertEquals(expectedSize, actualSize);
}
Вместо этого я мог бы написать так:
@Test
public void testAdditionAndSize() {
assertEquals(3, list.size());
}
Разве пострадала читаемость?
Во-вторых, что делать вот с таким тестом?
@Test
public void testIsEmpty() {
assertTrue(!list.isEmpty());
}
Неужели тоже создавать 2 булевы переменные, а потом сравнивать их?
@Test
public void testIsEmpty() {
boolean res = false;
boolean actual = list.isEmpty();
assertEquals(res, actual);
}
И в-третьих, На мой взгляд, этот паттерн усложняет написание некоторых тестов. Например:
@Test
public void testCicleNext() {
assertEquals(8, list.next().getId());
assertEquals(7, list.next().getId());
assertEquals(9, list.next().getId());
assertFalse(list.hasNext());
assertEquals(8, list.next().getId());
assertTrue(list.hasNext());
}
Тут перед каждым ассертом придется создавать по 2 переменные. В результате тест разрастется
до огромных размеров и читать его по-моему станет только тяжелее.
Ответы
Ответ 1
В любом паттерне всегда можно найти такой случай, когда он выглядит нелепо. Я думаю,
что Вы как раз и привели такой пример.
Классические обоснования паттерна 3А (да, так он пишеться - три А) следующие:
код разделяется на три логические части.
функция тестирования четко тестирует две вещи, без побочных эффектов.
разберем первый тест:
assertEquals(3, list.size());
Здесь не всегда понятно, есть ли у функции size побочные эффекты (да, в этом случае
это звучит глупо, но в общем случае - нет). И допустим этот тест захотелось закомментировать
временно. А код в тесте ниже может неявно зависеть от побочных эффектов. И все, прилетели...
(да, в этом конкретном тесте одна строка, но кто его знает)
Во-вторых, что делать вот с таким тестом?
@Test
public void testIsEmpty() {
assertTrue(!list.isEmpty());
}
А этот тест просто нужно правильно написать. Где то так
@Test
public void testListIsNotEmpty() {
bool is_empty = list.isEmpty();
assertFalse(is_empty);
}
Видите разницу? нет переменных типа res, значение которых не понятно. Также, если
список не должен быть пуст, то это и нужно тестить, а не противоположность (видите,
я даже имя теста поменял).
А теперь посмотрим на последний тест
@Test
public void testCicleNext() {
assertEquals(8, list.next().getId());
assertEquals(7, list.next().getId());
assertEquals(9, list.next().getId());
assertFalse(list.hasNext());
assertEquals(8, list.next().getId());
assertTrue(list.hasNext());
}
В этом примере next модифицирует исходный объект. И если одну строку закомментировать,
то все, тест может порушиться.
Этот тест кстати можно переписать через цикл и сразу он стает красивее и проще. И
не нужно создавать кучу переменных.
@Test
public void testCicleNext() {
int test_data[] = {8,7,9,8};
for (int i = 0; i < test_data.length(); i++) {
int expect = test_data[i];
int actual = list.next().getId();
bool has_next = list.hasNext();
assertEquals(expect, actual);
assertTrue(has_next );
}
}
Теперь, если нужно добавить ещё 5-6 тестируемых значений, то это очень просто.
Мое личное мнение. Внутри тестируемых функций (то есть, assertEquals и подобных)
не должно быть сложных конструкций. На то они и юнит тесты, что бы тестировать минимальную
единицу кода. И если size еще как то допустим, то методы, которые модифицируют объект
- нет.
Если у меня возникает ситуация, что мне нужно продебажить функцию/метод, который
я тестирую, то это первый звонок, что функция сложная и ее нужно разбить или переписать.
Если тест стает очень большим (а Вы на это жалуетесь), значит тест написан неверно
- его нужно либо разбить на два поменьше, либо написать правильно (я, к примеру в плюсах
люблю для этого делать макросы:) ).
Но в любом случае не нужно страдать паттернами головного мозга и применять их везде
и всюду. Нужно применять их по надобности.
я давно не писал на Java, поэтому в коде могут быть мелкие опечатки.
Ответ 2
Arrange-Act-Assert - это шаблон для форматирования Unit тестов. Обозначающий разделения
теста на 3 части
Arrange - все необходимые подготовки и входные данные
Act - собственно вызов того метода который вы тестируете
Assert - проверки, что метод делает то что надо
В ваших пример со списком должно быть что-то такое:
@Test
public void testAdditionAndSize() {
// Arrange - подготавливаем данные для теста
SomeClass obj = new SomeClass;
// Act - выполняем метод add
list.add(obj);
// Assert - проверяем, что объект добавился
assertEquals(1, list.size());
assertEquals(obj, list.get(1));
// другие тесты проверяющие что добавился правильный объект, что он не изменился
и т.д.
}