Используйте стандартную библиотеку Python для создания собственных сценариев модульного тестирования

Эта статья поможет вам научиться модульному тестированию вашего собственного приложения на Python. Исходя из официальной документации, unittest:

«… Изначально был вдохновлен JUnit и имеет тот же оттенок, что и основные фреймворки модульного тестирования на других языках. Он поддерживает автоматизацию тестирования, совместное использование кода настройки и выключения для тестов, объединение тестов в коллекции и независимость тестов от структуры отчетности ».

unittest - это стандартная библиотека для Python, поддерживающая объектно-ориентированный код. Он имеет следующие особенности:

  • text fixture - test fixture обозначает подготовку, необходимую для выполнения одного или нескольких тестов и любых связанных действий по очистке. Это может включать, например, создание временных или прокси-баз данных, каталогов или запуск серверного процесса.
  • test case - test case - это отдельная единица тестирования. Он проверяет конкретный ответ на определенный набор входных данных. unittest предоставляет базовый класс TestCase, который может использоваться для создания новых тестовых примеров.
  • test suite - test suite - это набор test case, test suite или и то, и другое. Он используется для агрегирования тестов, которые должны выполняться вместе.
  • test runner - test runner - это компонент, который управляет выполнением тестов и предоставляет результат пользователю. Бегун может использовать графический интерфейс, текстовый интерфейс или возвращать специальное значение, чтобы указать результаты выполнения тестов.

В этом руководстве мы сосредоточимся на test case. Перейдем к следующему разделу, чтобы начать писать код на Python.

Базовые концепты

Импортировать

Прежде всего, вам нужно импортировать модуль, объявив его поверх вашего файла Python:

import unittest

Прецедент

Чтобы создать testcase, вы должны создать класс, наследующий класс unittest.TestCase

class TestFunctions(unittest.TestCase):

Методы испытаний

Метод тестирования можно создать, добавив к ним префикс test. Любая функция, которая начинается с ключевого слова test, будет рассматриваться как метод модульного тестирования.

def test_some_function(self):

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

def remove_whitespace(text):
    result = text.replace(' ', '')
    return result

Затем мы должны создать тестовый метод, который проверяет функцию, которую мы определили выше. Я просто использую то же имя и добавлю к нему ключевое слово test. Не стесняйтесь называть его как хотите, если оно начинается с ключевого слова test. Внутри функции вам нужно определить проверку ожидаемого результата с помощью вызова assertEqual.

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

Главный

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

if __name__ == '__main__':
    unittest.main()

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

python -m unittest myFile.py

Вы можете указать дополнительные параметры. Общие параметры:

  • -b — Стандартный поток вывода и стандартный поток ошибок буферизуются во время выполнения теста. Вывод при прохождении теста отбрасывается. Выходные данные обычно отражаются эхом при неудачном тесте или ошибке и добавляются к сообщениям об ошибках.
  • -c - Control-C во время выполнения теста ожидает завершения текущего теста, а затем сообщает все результаты на данный момент. Второй Control-C вызывает обычное исключение KeyboardInterrupt.
  • -f — Остановить тестовый прогон при первой ошибке или сбое.
  • -k - запускать только тестовые методы и классы, соответствующие шаблону или подстроке. Этот параметр можно использовать несколько раз, и в этом случае будут включены все тестовые примеры, соответствующие заданным шаблонам. Шаблоны, содержащие подстановочный знак (*), сопоставляются с именем теста с помощью fnmatch.fnmatchcase(). В противном случае используется простое сопоставление подстрок с учетом регистра. Шаблоны сопоставляются с полным именем метода тестирования, импортированным загрузчиком теста.
  • -v - Подробный вывод

Допустим, вам нужен подробный вывод. Вы запускаете следующую команду.

python -m unittest -v myFile.py

Если вы используете Jupyter Notebook, вы столкнетесь со следующей ошибкой:

Вы можете легко решить эту проблему, изменив основной код на этот:

unittest.main(argv=['ignored'], exit=False)

Аргумент списка предназначен для предотвращения unittest поиска sys.argv, поскольку он запускает Notebook, в то время как exit=False предназначен для предотвращения unittest выключения ядра. Также в список можно добавить параметры. Например:

unittest.main(argv=['ignored', '-v'], exit=False)

При запуске скрипта вы должны увидеть следующий результат:

Вторая тестовая функция

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

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

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

Следующий результат будет выведен на консоль при запуске скрипта.

Давайте изменим значение сравнения второй строки с 8.0 на 7.0, чтобы проверить, каким будет результат при запуске скрипта. Вы должны увидеть следующий результат:

Как видите, логическая ошибка будет отражена в журнале. В этом случае сгенерированный результат 8.0 не равен ожидаемому результату 7.0. Это будет полезно для определения проблемы при запуске модульного теста после изменения базовой функции.

Утверждать

В этом разделе мы подробно рассмотрим вышеперечисленные другие типы assert, которые можно использовать в вашем тестовом примере.

assertNotEqual

Это используется для проверки того, совпадает ли левый параметр с правым параметром.

self.assertNotEqual(1, 2)

assertTrue

Это проверяет, оценивается ли входной параметр как true. Не считая обычных значений True и False. Вы также можете использовать другие типы данных. 0 будет оцениваться как False, а любое другое значение будет рассматриваться как True. Вы также можете использовать его для оценки строки. Пустая строка считается False.

self.assertTrue(1)

assertFalse

assertFalse - противоположность assertTrue. Он используется для оценки того, равен ли входной параметр False или нет.

self.assertFalse(False)

assertIs

assertIs можно использовать, чтобы проверить, являются ли оба входных параметра равными объектами. Вы также можете использовать его для проверки типа объекта. Прежде чем называть это, создадим несколько переменных:

a = 2
b = 2
c = 3
basket = [1, 2]

Продолжайте писать код для assertIs:

self.assertIs(a, b)
self.assertIs(type(a), int)

assertIsNot

Это проверяет, не являются ли оба входных параметра одинаковыми объектами. Проверьте точный тип и класс.

self.assertIsNot(a, c)
self.assertIsNot(a, '2')

assertIsNone

Если вы хотите проверить None, вы можете использовать assertIsNone.

self.assertIsNone(None)

assertIsNotNone

Противоположность assertIsNone. Используется для проверки объекта, отличного от None.

self.assertIsNotNone('string')

assertIn

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

self.assertIn(a, basket)

assertNotIn

Модуль unittest также предоставляет нам assertNotIn, который помогает проверить, не является ли левый входной параметр частью элемента в правом параметре.

self.assertNotIn(c, basket)

assertIsInstance

Проверьте, является ли левый входной параметр объектом правого входного параметра. Он основан на функции isinstance(). Это означает, что он вернет True даже для подкласса.

self.assertIsInstance(a, int)

assertIsNotInstance

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

self.assertNotIsInstance(a, float)

Оператор сравнения

Этот модуль также содержит утверждение для математической операции между двумя объектами.

  • assertGreater
  • assertGreaterEqual
  • assertLess
  • assertLessEqual
self.assertGreater(c, a)
self.assertGreaterEqual(a, b)
self.assertLess(a, c)
self.assertLessEqual(a, b)

Настройка и разборка

настраивать

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

def setUp(self):
    print('Setup')

срывать

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

def tearDown(self):
    print('TearDown')

Если у вас есть три метода тестирования, вы увидите три строки вывода Setup и три строки вывода TearDown.

Пропустить тест

Декораторы

Кроме того, unittest также позволяет нам настроить decorator, чтобы пропустить определенный тестовый класс или метод. Давайте проверим это с помощью следующих декораторов:

  • skipIf
  • skipUnless

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

skipIf

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

import logging
@unittest.skipIf(logging.__version__ == '0.5.1.2', "not supported in this library version")
def test_format(self):
    #Tests that work for only a certain version of the library.
    print(logging.__version__)

Вы должны увидеть следующий результат:

skipUnless

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

@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_windows_support(self):
    # windows specific testing code
    print(sys.platform)

Если вы используете Windows, на консоль будет выведен следующий результат:

skipTest

Вы также можете пропустить тест, оценив его внутри функции. Вы можете легко реализовать простой оператор условия, который вызовет вызов skipTest. Следующая тестовая функция будет работать только в субботу или воскресенье:

import datetime
def test_weekend(self):
    index = datetime.datetime.today().isoweekday()
    if(index >= 1 and index <= 5):
        self.skipTest("Not weekend")

Не стесняйтесь проверить полный код в следующей таблице:

При запуске сценария вы должны увидеть следующий результат.

Заключение

Подведем итоги тому, что мы узнали сегодня.

Мы начали с простого объяснения основных понятий модуля unittest. Мы написали простой тестовый скрипт и запустили его.

После этого мы подробно изучили различные типы ресурсов, доступных в модуле unittest.

В модуле также есть функция setUp, которая действует как вызов инициализации для каждого метода тестирования. Кроме того, функцию tearDown можно использовать для удаления или завершения объектов после каждого вызова тестового метода.

Наконец, мы протестировали skip decoractors и функцию, которая позволяет пропустить класс или метод проверки частиц.

Спасибо за чтение, надеюсь, вам понравилось. Увидимся снова в следующем выпуске!

использованная литература

  1. Документация Юниттеста
  2. Устранение неполадок при запуске unittest в Jupyter Notebook