Модульное тестирование - начало работы

14

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

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

  2. Большинство наших кодов опираются на внешние источники. Однако после рефакторинга нашего кода, даже если он сломает исходный код, наши тесты все равно будут работать нормально, поскольку внешние источники - это просто козлы в наших тестовых примерах. Разве это не противоречит цели модульного тестирования?

Извините, если я звучу глупо, но я подумал, что кто-то может немного просветить меня.

Заранее спасибо.

treecoder
источник

Ответы:

7

Мои 0.02 $ ... это немного субъективно, так что возьмите с собой немного соли, но, надеюсь, это заставит вас задуматься и / или зажечь какой-то диалог:

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

    В большинстве случаев вы должны быть в состоянии написать код, который может быть отправлен в производство и легко тестируемым. Хорошим местом для начала может быть изучение шаблонов и структур внедрения зависимостей. (Или другие философии для вашего языка / платформы выбора).

  2. Это верно, что внешние реализации могут повлиять на ваш код. Однако обеспечение правильной работы вашего кода как части большой системы является функцией интеграционного тестирования. (Который также может быть автоматизирован с различной степенью усилия).

    В идеале ваш код должен опираться только на контракты API любых сторонних компонентов, что будет означать, что до тех пор, пока ваши макеты соответствуют правильному API, ваши модульные тесты все еще дают ценность.

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

Чарли
источник
5
  1. Попробуйте сначала написать свои тесты. Таким образом, у вас будет прочная база для поведения вашего кода, и ваш тест станет контрактом для требуемого поведения вашего кода. Таким образом, изменение кода для прохождения теста становится «изменением кода для выполнения контракта, предложенного тестом» вместо «изменения кода для прохождения теста».
  2. Хорошо, будьте осторожны с разницей между заглушками и издевательствами. Отсутствие изменений в коде - это характерное поведение заглушек, но не имитаций. Начнем с определения макета:

    Объект Mock заменяет реальный объект в условиях тестирования и позволяет проверять вызовы (взаимодействия) против себя как часть системного или модульного теста.

    -Искусство модульного тестирования

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

ruhsuzbaykus
источник
1

Задать хороший вопрос вовсе не глупо.

Я отвечу на ваши вопросы.

  1. Цель юнит-тестирования - не тестировать код, который вы уже написали . Это не имеет понятия времени. Только в TDD вы должны тестировать сначала, но это не относится строго к любому виду юнит-тестирования. Суть в том, чтобы иметь возможность автоматически и эффективно тестировать вашу программу на уровне класса. Вы делаете то, что вам нужно сделать, чтобы попасть туда, даже если это означает изменение кода. И позвольте мне рассказать вам секрет - это часто означает это.
  2. Когда вы пишете тест, у вас есть 2 основных варианта, чтобы убедиться, что ваш тест правильный:

    • Измените входные данные для каждого теста
    • Напишите тест-кейс, который гарантирует, что ваша программа работает должным образом, а затем напишите соответствующий тест-кейс, который гарантирует, что ваша программа работает не так, как она должна

    Вот пример:

    TEST(MyTest, TwoPlusTwoIsFour) {
        ASSERT_EQ(4, 2+2);
    }
    
    TEST(MyTest, TwoPlusThreeIsntFour) {
        ASSERT_NE(4, 2+3);
    }
    

    Кроме того, если вы тестируете логику внутри своего кода (не сторонних библиотек), то вполне нормально, что вы не беспокоитесь о другом нарушении кода в этом контексте. По сути, вы тестируете, как ваша логика оборачивается и использует сторонние утилиты, что является классическим сценарием тестирования.

Как только вы закончите тестирование на уровне класса, вы можете приступить к тестированию интеграции между вашими классами (посредниками, насколько я понимаю) и сторонними библиотеками. Эти тесты называются интеграционными тестами, и в них используются не макеты, а конкретные реализации всех классов. Они немного медленнее, но все же очень важны!

Ям Маркович
источник
1

Похоже, у вас есть монолитное приложение, которое делает все void main(), начиная с доступа к базе данных и заканчивая генерацией вывода. Здесь есть несколько шагов, прежде чем вы сможете начать надлежащее модульное тестирование.

1) Найдите фрагмент кода, который был написан / скопирован более одного раза. Даже если это просто string fullName = firstName + " " + lastName. Разбейте это на метод, например:

private static string GetFullName (firstName, lastName)
{
    return firstName + " " + lastName;
}

Теперь у вас есть фрагмент кода, который можно протестировать на модуле, каким бы тривиальным он ни был. Напишите для него модульный тест. Промойте и повторите этот процесс. В конечном итоге вы получите массу логически сгруппированных методов и сможете извлечь из нее несколько классов. Большинство из этих классов будут тестируемыми.

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

Брайан Бетчер
источник
0

Какой-то умник сказал: «Если это сложно проверить, возможно, это плохой код». Вот почему это не плохо, чтобы переписать код, чтобы иметь возможность протестировать его. Код с внешними зависимостями ОЧЕНЬ труден для тестирования, он представляет риск для кода, и его следует избегать везде, где это возможно, и концентрироваться в областях интеграции вашего кода, fx. классы фасадов / ворот. Это значительно облегчит изменение внешнего компонента.

Morten
источник