Страницы

Поиск по вопросам

четверг, 23 января 2020 г.

Чем хорош паттерн Arrange, Act, Assert и стоит ли им пользоваться всегда?

#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)); // другие тесты проверяющие что добавился правильный объект, что он не изменился и т.д. }

Комментариев нет:

Отправить комментарий