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

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

В этой статье я буду:

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

Для этой проблемы нам нужно создать функцию, которая будет проверять, содержатся ли символы в первой входной строке, firstStr, по порядку во второй входной строке, secondStr, и возвращает True или False в зависимости от того, есть это или нет.

  1. Строки 8 и 9 проверяют, длиннее ли первый вход, чем второй вход, и в этом случае нет возможности, чтобы второй вход содержал первый, и поэтому мы возвращаем False. Эти строки также будут принимать случай, когда в качестве первого ввода задана пустая строка. Обычно это возвращает True, потому что второй вход является допустимой строкой. В нашем случае мы не хотим, чтобы пустая строка была действительной, поэтому мы вернем False.
  2. Строки 11 и 12, переменные secondStrChar и firstStrChar отслеживают, какие символы в firstStr и secondStr мы в настоящее время сравнение.
  3. Затем в строках с 14 по 24 мы просматриваем оба строковых входа до тех пор, пока один из указателей (строки 11 и 12) не превысит длину своей строки, и в этот момент каждый символ в firstStr не будет найден в secondStr или secondStr полностью пронумерованы до firstStr.
  4. Строки с 21 по 24 условно возвращают True или False в зависимости от того, какая строка была пронумерована первой. И мы знаем это по позициям указателей в строках 11 и 12.

Если вы хотите попробовать эту задачу на себе, я черпал вдохновение в этой задаче на LeetCode.

Теперь, когда я объяснил проблему, решения которой мы будем тестировать, перейдем к тестам.

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

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

  1. Здесь мы создаем функцию, которая обертывает все наши тесты, и когда мы запускаем этот файл с Python3, эта функция будет вызываться в конце, эффективно вызывая isSubsequence для каждого из наших тестов.
  2. Это распечатанные результаты isSubsequence, вызываемые для каждого теста. Чтобы определить, получили ли мы правильный результат, мы должны сопоставить каждый возврат в нашей консоли с каждым вызовом функции в нашем файле, сравнивая возврат консоли с ожидаемыми результатами, прокомментированными под каждым вызовом.

Как вы понимаете, до сих пор мы только увеличивали нашу сложность, добавляя дополнительные строки кода и усложняя определение того, дал ли конкретный тест желаемый результат. Чтобы уменьшить эту сложность, мы должны найти способ уменьшить количество строк кода, которые мы используем в нашем тестировании и выводах на консоль, которые разъясняют результаты для пользователя.

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

Хорошо, я знаю, что у меня здесь везде стрелки, но обещаю, что мало что изменилось. Я только что создал функцию runTest для вызова isSubsequence и сравнения ее возврата result с ожидаемым, expectedResult .

  1. Я создал функцию runTest, которая будет принимать аргументы, которые я хочу запустить для isSubsequence, и результат, который я ожидаю получить, и возвращающее сообщение в зависимости от того, соответствует ли ожидаемый результат фактический выход.
  2. Входные данные, которые я передаю, находятся в массиве только потому, что есть несколько аргументов для доступа для isSubsequence. Вывод является логическим, потому что это результат, который я ожидаю от isSubsequence на основе ввода.
  3. Как упоминалось в предыдущих шагах, мы назовем наши параметры input для представления значения, которое мы передадим в isSubsequence, и expectedResult для представления значения. который должен быть возвращен из isSubsequence, если isSubsequence работает правильно. Когда мы сохраняем результат isSubsequence, мы должны помнить, что наши входные значения передаются в виде массива, и поэтому нам нужно будет получить доступ к значениям как таковым.
  4. После того, как у нас будет хранилище значений для result, мы сравним result с expectedResult и условно вернем сообщение на консоль (например, «Тест пройден!» Или «Результат не соответствует ожидаемому результату»).
  5. У нас есть консольное сообщение, показывающее, что каждый тест пройден, что означает, что наше решение работает правильно, однако нет способа определить, к какому тесту принадлежит каждое из этих сообщений.

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

  1. Я помещаю все тесты в хеш. Хеши идеальны, потому что у вас всегда будет неизвестное количество тестов для разных задач, и это предотвращает выход сложности из-под контроля. Кроме того, список аргументов обычно статичен. Наконец, идеален хэш, потому что вы можете идентифицировать тест по его уникальному ключу.
  2. Перечислить с помощью хэша tests, отделяя ключ для использования в качестве testId, массив, содержащий входные данные для isSubsequence (который мы уже передавали в runTest в виде массива, поэтому нам не нужно ничего менять) и, наконец, наш expectedResult.
  3. Мы берем ключ из tests, добавляем дополнительный параметр runTest, который мы назовем testId, и передаем ключ в testId. Мы будем использовать testId, чтобы идентифицировать тест, на который ссылаются наши сообщения консоли, чтобы нам не приходилось его искать.

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

Следите за обновлениями, я буду писать о модуле unittest в Python и библиотеке Jest в JavaScript.