Какой лучший способ проверить компаратор, который использует другой компаратор в Java? - PullRequest
0 голосов
/ 27 апреля 2018

С точки зрения TDD, я узнал, что когда что-то поведенческое нарушается, должен проваливаться только один тест - другие ошибки обычно вводят в заблуждение. Если это правда, и у меня есть два компаратора, и один из компараторов использует другой компаратор, как вы это тестируете? Я думал об использовании макета для «субкомпаратора», но как бы вы внедрили этот макет, например, при сортировке списка с использованием «родительского компаратора»?

Например:

public class SomeParentComparator implements Comparator<SomeType> {

    private static final SomeSubComparator subComparator1 = new SubComparator();
    private static final SomeOtherSubComparator subComparator2 = new SomeOtherSubComparator();

    @Override
    public int compare(SomeType someType1, SomeType someType2) {
        return new CompareToBuilder()
                .append(someType1.foo, someType2.foo, subComparator1)
                .append(someType1.bar, someType2.bar, subComparator2)
                .toComparison();
    }
}

Предположим, что я уже тестировал «субкомпараторы» (SomeSubComparator и SomeOtherSubComparator). Как мне протестировать SomeParentComparator в этом случае, не имея «истинной зависимости» от субкомпараторов (например, макет субкомпараторов)? Действительно, это должно быть модульное тестирование «рабочего процесса» и просто убедиться, что «субкомпараторы» вызваны, верно? Как?

Ответы [ 4 ]

0 голосов
/ 30 апреля 2018

В этом случае я не стал бы беспокоиться о насмешках и проверил бы все это (все 3 компаратора) как единое целое.

Я думаю, что тесты могут выглядеть примерно так:

@Test
public void parent_with_smaller_foo_and_equal_bar_is_smaller() {
  var parentA = aParent().withFoo("A").withBar("C");
  var parentB = aParent().withFoo("B").withBar("C");

  assertThat(parentA).isLessThan(parentB);
}

@Test
public void parent_with_equal_foo_and_equal_bar_is_equal() {
  var parentA = aParent().withFoo("A").withBar("C");
  var parentB = aParent().withFoo("A").withBar("C");

  assertThat(parentA).isEqualByComparingTo(parentB);
}

и так далее. Если бы вы написали выше, как:

(A,C)<(B,C)
(A,C)=(A,C)

тогда, похоже, нам понадобится как минимум еще 3 случая, чтобы протестировать все три компаратора:

(B,C)>(A,C)
(A,B)<(A,C)
(A,C)>(A,B)

Я бы оставил SubComparator и SomeOtherSubComparator как закрытые для пакета классы и рассматривал бы их как детали реализации SomeParentComparator.

Имея всего 5 тестовых случаев, я не считаю, что найти причину сбоя - настоящая проблема.

0 голосов
/ 27 апреля 2018

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

  1. Структурируйте ваш модуль модульных тестов так, чтобы тесты для зависимых классов выполнялись только в том случае, если тесты для зависимых классов пройдены.

  2. Используйте что-то вроде Powermock, чтобы обойти инкапсуляцию во время модульного тестирования и внедрить ложные зависимости. Это позволит проходить тесты для зависимых классов, даже если зависимые классы не работают.

В приведенном вами примере я не вижу причин, по которым вы не можете сделать зависимость явной. Нет необходимости, чтобы поля были статичными - все объекты этого класса будут вести себя одинаково. Поэтому было бы лучше иметь явную коллекцию «субкомпараторов» и ожидать, что вызывающая сторона добавит их явно.

0 голосов
/ 27 апреля 2018

Я думаю, что вы неправильно истолковали совет TDD.

На самом деле у вас есть два класса подкомпараторов, которые можно протестировать независимо. Затем у вас есть «родительский» компаратор, который имеет экземпляры субкомпаратора в качестве аппаратных компонентов. Просто проверь их по отдельности ... без всяких причудливых издевательств.

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

Думая об этом по-другому, ответ @ ShanuGupta предполагает, что вы должны взломать абстракцию родительского компаратора, чтобы допустить насмешку. Вероятно, вы инкапсулировали экземпляры субкомпаратора по уважительной причине. Теперь вы можете создать родительский конструктор, используя DI ... но снова вы фактически открываете абстракцию для этого.

Или другим способом. Предположим, вы взломали инкапсуляцию. Теперь у вас есть родительский класс, который (концептуально) должен работать для всех возможных классов субкомпараторов. (Потому что кто-то может изменить код для использования других компараторов без изменения самого родителя.) Но это потенциально означает, что у вас есть более сложный набор поведений для тестирования.

0 голосов
/ 27 апреля 2018

Ваш SomeParentComparator очень трудно проверить самостоятельно, только потому, что вы непосредственно инициализируете subComparator1 и subComparator2 в переменных экземпляра объекта класса.

Я бы порекомендовал иметь сеттеры и геттеры для этих двух полей и инициализировать их, используя сеттеры или конструктор.

Тогда вы можете использовать setters, чтобы установить ваш смоделированный subComaparators.

Вы также можете создать фиктивные данные, где subComparator сравнение не имеет значения. Я могу привести некоторую аналогию, скажем, Вы хотите отсортировать People объекты по их имени и фамилии. Ваш родительский компаратор сортирует по имени, а субкомпаратор работает по фамилии. Тогда ваши фиктивные данные будут представлять собой список People, чьи фамилии совпадают.

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