Рубрики
Без рубрики

Переосмысление Издевается

В эти дни я в основном использую Java для своей профессиональной карьеры (примерно с этим я работал… Помечено программным обеспечением, tdd, java.

В эти дни я в основном использую Java для своей профессиональной карьеры (примерно я работаю с Java уже год). Я также в основном использую Spring framework вместе с его дополнительными библиотеками на работе. Одна из вещей, которые мне нравятся в Spring, – это возможность внедрения зависимостей, которую она предоставляет, в первую очередь через аннотацию @Autowired . Этот быстрый и простой способ внедрения зависимостей также дополнительно усиливает преимущества тестирования, поскольку Spring также предоставляет простой и простой способ получения этих зависимостей во время тестирования ( @Импорт и @ContextConfiguration , чтобы назвать несколько) значительно упрощает тестирование. Я также хорошо использую Mockito который значительно облегчает мою жизнь в разработке с помощью mocks , и этот пост посвящен mocks а также мой опыт и мысли о них.

Этот вопрос был рассмотрен во множестве других сообщений, одним из которых я счел наиболее полезным Сообщение Мартина Фаулера . По словам самого Мартина Фаулера:

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

В основе mocking лежит проверка поведения, которая представляет собой способность утверждать, что произошло во время выполнения теста, а не только то, что произвела тестируемая система. Мартин Фаулер сделал четкое различие и акцент на этой части, о чем вы можете прочитать в его посте.

class ClassA {
    ClassB dependency;
    boolean shouldDoFunc;

    ClassA(ClassB dependency, boolean shouldDoFunc) {
        this.dependency = dependency;
        this.shouldDoFunc = shouldDoFunc;
    }

    void func() {
        if (this.shouldDoFunc) {
            this.dependency.doFunc();
        }
    }
}

class ClassB {
    void doFunc() {
        // do something
    }
}

// test code below
void test_func_whenShouldNotDoFunc_notCallDoFunc() {
    ClassB classB = mock(ClassB.class);
    ClassA classA = new ClassA(classB, false);

    classA.func();
    verify(classB, never()).doFunc();
}

Приведенный выше пример чрезвычайно надуман, но он едва показывает пример использования mocks. Вызов функции verify говорит о том, что мы пытаемся проверить, что, когда следует выполнить функцию , мы ожидаем, что ClassB.doFunc вызываться не будет.

Альтернативой использованию mocks выше было бы предоставить фактический экземпляр ClassB (не mocked) и свериться с |/результатами после вызова ClassA.func . Теперь в приведенном выше примере никакие значения не возвращаются ни из одного из вызовов функций, поэтому нельзя ожидать никакого возвращаемого значения, однако часто такие функции вызывают побочные эффекты. Общие побочные эффекты включают вызов некоторого API, который вызывает некоторые изменения, изменение настроек среды, выполнение изменений в базе данных, таких как обновления, изменение состояния ClassB и многое другое. Такие побочные эффекты поддаются проверке и проверке (если функция не вызывает побочных эффектов, я задаюсь вопросом, почему эта функция существует).

Из моего опыта использования mocks до сих пор следует, что создавать тесты было полезно. Для определенного тестирования может потребоваться дорогостоящая процедура настройки или проверки, из-за чего тесты выполняются медленно, но и разрабатываются медленнее. Альтернативой moocs может быть создание подделок или заглушек (отдельный вид объекта тестирования), что также может потребовать дополнительных усилий по разработке, а также может потребовать собственного обслуживания. Хотя большая часть этого преимущества проистекает из того факта, что многие языки имеют хорошо протестированную и распространенную библиотеку mocking.

Mocks также упрощает создание действительно модульных тестов, и под unit здесь я подразумеваю тесты, которые тестируют только один класс/объект. Макеты позволяют мне сосредоточить внимание и внимание тестов на конкретном тестируемом модуле и не беспокоиться о других взаимодействующих элементах. В приведенном выше примере тест выполняется намеренно для ClassA.func поведение, когда оно находится в определенном состоянии. В более сложных тестируемых объектах это делает желаемое мелкозернистое тестирование гораздо более доступным и управляемым.

Самый большой недостаток использования mocking заключается в том, что для проверки с помощью mocks требуется тестирование белого ящика, для проверки с помощью mocks нам нужно знать, какие функции вызываются, что, по сути, сводится к знанию некоторого уровня детализации о реализации тестируемого модуля. В настоящее время я думаю об этом как о отражении для тестирования . Отражение – это способность проверять программные конструкции во время выполнения некоторого кода внутри самого кода. Часто это включает в себя передачу имен функций или полей в виде строк специализированным API-интерфейсам отражения. Поскольку доступ осуществляется через строки, IDE не помогут вам во время рефакторинга, что затрудняет обнаружение ошибок во время изменений в базе кода. К счастью, тестирование – это один из инструментов, которые у нас есть, чтобы защитить себя от ошибок в программировании, пока тесты не начнут подводить нас.

Использование возможностей отражения требует, чтобы программист имел четкие знания о том, что есть, а что нет, и статически включал эти знания в код, и это тот же случай, когда мы используем mocks. Теперь такие библиотеки, как Mockito действительно позволяет использовать рефакторинг через IDE, в отличие от reflection, но mocking не позволяет нам легко изменять тесты при изменении вызовов зависимостей в тестируемом коде. Это создает несоответствие между ожиданиями и реальностью. Например, если в Class выше мы изменим вызов doFunc на другую функцию doFunc2 , тест все равно пройдет, но по очень неправильной причине! Реальность такова, что do Функционирует больше не выполняется вызов зависимости, но тест по-прежнему ожидает, что он будет выполнен как вызов, и так получилось, что тест проверял, что никаких вызовов не должно было быть сделано. Следующий тест завершится неудачей:

void test_func_whenShouldDoFunc_callDoFunc() {
    ClassB classB = mock(ClassB.class);
    ClassA classA = new ClassA(classB, true);

    classA.func();
    verify(classB, once()).doFunc();
}

но теперь тест проваливается по неправильной причине! Тест завершится неудачей, потому что тест ожидает неправильного результата, а не потому, что тестируемый модуль ведет себя неправильно. Это тестовое техническое обслуживание. Мартин Фаулер называет это соединением внедрения и тестирования.

Одной из убедительных альтернатив издевательству является предоставление функций в качестве параметров тестируемому модулю вместо целых зависимостей объекта. Возможность передавать функции в качестве параметров называется Первоклассной функцией . Рассмотрим этот пример в псевдо-Javascript

function funcToTest(dependentFunction) {
    if (some condition) {
        dependentFunction();
    }
}

test_funcToTest_whenConditionNotMet_doNotCallDependentFunction() {
    // do something to cause condition to not be met
    counter = 0
    funcToTest(() -> counter + 1)
    if (counter != 0) {
        throw new TestFailed()
    }
}

Переданная в зависимая функция – это просто функция, переданная в качестве параметра, которая вызывается при выполнении некоторого условия внутри funcToTest . Обратите внимание, что в тесте мы также проверяем поведение func Для проверки при некоторых условиях, аналогично примеру с ClassA . Как в тесте с использованием mocks, так и в первоклассной функции мы видим, что поведение проверяется, и это не должно вызывать удивления. Мы можем думать об объектах как о экземплярах пространств имен/группировок для функций (с возможностью поддерживать и собирать некоторую форму внутреннего состояния), и поэтому, когда мы передаем объект как зависимость, его можно рассматривать как передачу в коллекции функций. Разница в том, что когда мы используем mocks, мы должны явно указывать интересующую нас зависимую функцию, в то время как с первоклассными функциями зависимая функция оговаривается в формальном синтаксисе (исключая тот факт, что в Javascript можно вызывать функции без их параметров). С помощью mocks тесты могут потерпеть неудачу/пройти по причинам несоответствия между ожиданиями и реальностью, с первоклассными функциями эта возможность значительно уменьшается, и если это произойдет, скорее всего, ошибка программиста.

Однако на самом деле я вообще не использовал первоклассные функции в качестве альтернативы mocks. Во многом причина в том, что в Java гораздо проще работать с объектами и макетами. Хотя в Java есть лямбда-функции и @Функциональный интерфейс не так уж легко создавать классы и методы, которые постоянно используют первоклассные функции. Но это, безусловно, техника, которую я хотел бы изучить и более осознанно использовать в будущем.

Оригинал: “https://dev.to/btruhand/re-thinking-mocks-19mi”