Модульные тесты: СУХОЙ против предсказуемости - PullRequest
4 голосов
/ 28 октября 2009

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

public Node buildNode(String path, String name, Map<String, Object> attributes);

В зависимости от предоставленных параметров результирующий объект Node будет отличаться, и поэтому нам необходимо протестировать различные возможности. Если мы стремимся к предсказуемости, мы могли бы написать два автономных модульных теста, как указано в первом примере, но если мы стремимся к DRY, мы бы предпочли добавить общий вспомогательный метод, такой как во втором примере:

EXAMPLE1:
@Test
public void testBuildBasicNode() {
  Node node = testee.buildNode("/home/user", "Node", null);
  assertEquals("/home/user/Node", node.getAbsolutePath());
  assertEquals(false, node.isFolder());
}

@Test
public void testBuildAdvancedNode() {
  Map<String, Object> attributes = new HashMap<String, Object>();
  attributes.put("type", NodeType.FOLDER);
  Node node = testee.buildNode("/home/user", "Node", attributes);
  assertEquals("/home/user/Node", node.getAbsolutePath());
  assertEquals(true, node.isFolder());
}

EXAMPLE2:
@Test
public void testBuildBasicNode() {
  Node node = testee.buildNode("/home/user", "Node", null);
  Node comparisonNode = buildComparisonNode("/home/user", "Node", null);
  assertEquals(comparisonNode, node);
}

@Test
public void testBuildAdvancedNode() {
  Map<String, Object> attributes = new HashMap<String, Object>();
  attributes.put("type", NodeType.FOLDER);
  Node node = testee.buildNode("/home/user", "Node", attributes);
  Node comparisonNode = buildComparisonNode("/home/user", "Node", attributes);
  assertEquals(comparisonNode, node);
}

private Node buildComparisonNode(String path, String name, Map<String, Object> attributes) {
  // Not just a duplicate of the buildNode method,
  // can be more trivial if we only limit it to unit tests that share some common attributes
  ...
}

Моя проблема с первым примером (предсказуемость) заключается в том, что если какие-либо функциональные возможности меняются (например, скажем, как должен быть отформатирован AbsolutePath), он требует изменений во всех моих модульных тестах. Моя проблема со вторым примером заключается в том, что buildComparisonNode ощущается как нечто, что также должно быть протестировано, и я, конечно, не хочу начинать писать тесты для тестов.

Также, как заключительная мысль, вы бы объявили конечные переменные для литеральных строк, использованных в примерных модульных тестах, или они в порядке, как они есть?

Ответы [ 3 ]

7 голосов
/ 28 октября 2009
  1. Хороший вопрос. Я слышал ранее, что «модульные тесты могут быть влажными, но не мокрыми ...» Для обеспечения удобства проведения теста фокус (или предсказуемость) является ключевым. Чем больше ваша команда, тем важнее это для вас. Еще одна вещь, которую следует учитывать: если есть специальный тестовый вспомогательный код, он может стать API сам по себе, поэтому, если все знают, как его использовать, может быть плохой идеей. Мое эмпирическое правило: я удалю дублирование, если смогу сделать это с помощью IDE в автоматическом рефакторинге, и я могу дать ему хорошее имя.

  2. Предложение ... ознакомьтесь с Test Data Builder записью шаблона Nat Pryce о более удобном / расширяемом подходе.

4 голосов
/ 28 октября 2009

Хотя DRY применим к производственному коду, он не всегда применим к модульным тестам. Вы действительно хотите, чтобы каждый тест был независимым друг от друга, и часто это означает повторение. С другой стороны, я считаю полезным сгруппировать определенные вещи в вспомогательные методы, которые используются всеми тестами, если только они не объединяют вместе тесты, тогда все должно быть хорошо. Одно из мест, где я обычно уменьшаю дублирование, - это использование создателей тестовых данных для создания объектов, которые существуют в определенном состоянии в моем тесте.

Мое эмпирическое правило заключается в том, чтобы мои тесты были как можно меньше и читабельнее. Если использование DRY может помочь достичь этого, тогда я использую его. Если нет, то нет. : -)

Надеюсь, это поможет. Я не мировой эксперт по юнит-тестированию, поэтому могу ошибаться. : -)

0 голосов
/ 28 октября 2009

Еще одна вещь, о которой следует подумать - часто, когда вы удаляете дублирование в тестах, он говорит вам, что что-то представляет собой производственный код или дизайн, который ожидает по уважительной причине (использование в производственной кодовой базе) для его перемещения в производственный код. Я не могу точно вспомнить, откуда я получил этот ах-ха-ха момент ... но я думаю, что это было связано с TDD, как будто вы имеете в виду .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...