×

Тестирование по-пайтоновски. Введение

Написание любой программы, длиннее 100 строчек, практически немыслимо без создания модульных тестов (unit test). По сути своей, модульный тест — специальный код, автоматически проверяющий функциональность маленького участка кода. Обычно модульные тесты пишутся так чтобы проверять весь диапазон проверяемого кода, а кроме того, часть тестов составляется так чтобы заведомо «не срабатывать».

Рассмотрим простой пример. Пусть у нас есть процедура нахождения суммы квадратов:

def sum_kv(x, y):
    return x*x+y*y

Процедура теста для этой функции будет иметь следующий вид:

def test_our_proc():
    assert sum_kv(2, 3) == 13
    assert not sum_kv(3,4)==10

Модульные тесты используются по-разному, в некоторых случаях — они пишутся еще до написания кода программы (например, популярная техника TDD проповедует такой подход). Иногда, тесты используются в процессе рефакторинга, чтобы не «сломать» функциональность программы, в этом случае тесты пишутся перед началом изменения программы. Некоторые разработчики пишут тесты «по мотивам» найденных ошибок — так их удобней исправлять и отслеживать. Существует еще множество способов использования модульных тестов, но их рассмотрение выходит за рамки данной статьи.

В ходе разработки на Python код для тестирования обычно вызывается следующим образом:

def my_test():
    ...............

if __name__ == 'main__':
    my_test ()

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

Кроме того, это заставляет отказаться от использования тестов в исполняющихся программах.

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

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

Установка пакета с помощью setuptools не представляет никаких затруднений. Достаточно скомандовать:

easy_install nose

И все будет готово, естественно при наличии у вас Интернета. Если с Интернетом плохо, можно скачать упакованную версию на сайте разработчиков.

Теперь можно написать простейший тест:

def test_math():
    assert 2*2==4

Сохраним его в файле test_it.py. Потом перейдем в каталог с файлом и запустим скрипт nosetests. В результате получим:

.
----------------------------------------------------------------------
Ran 1 test in 0.016s

OK

Наш единственный тест успешно выполнился.

Если необходимо получить более подробный отчет, можно вызвать nosetests с ключем -v:

test_it.test_math ... ok

----------------------------------------------------------------------
Ran 1 test in 0.031s

OK

Можно использовать альтернативный синтаксис тестов — в виде классов:

class TestExample:
    def test_char(self):
        assert 'z' == 'z'

Также возможно использоавние тестов — наследников класса unittest.TestCase

class DerivedTest(unittest.TestCase):
    def test_str(self):
        self.assert_("123" == "123")

В более обобщенной форме, модульный тест может иметь следующую структуру:

def test():
    setup_test()
    try:
        do_test()
        make_test_assertions()
    finally:
        cleanup_after_test()

Тут setup_test() выполняет все предусловия — соеднияется с БД, создает объекты, устанавливает соединения и т.п. После этого do_test() и make_test_assertions() выполняют код теста и проводят проверку полученных результатов. В конце, независимо от результатов вызывается cleanup_after_test() для «наведения порядка», этот этап еще иногда называют teardown.

Обычно фреймворки для тестирования позволяют определить специальные вставки:

def test():
    do_test()
    make_test_assertions()

test.setUp = setup_test
test.tearDown = cleanup_after_test

Фреймворк автоматически обнаружит процедуры инициализации и завершения и вызовет их.

Также можно использовать запись в виде класса:

class TestClass:
    def setUp(self):
        ...
    def tearDown(self):
        ...
    def test_case (self):
        ...

В этом случае nose автоматически создаст объект, вызовет методы инициализации, потом методы тестирования и обеспечит вызов метода очистки.

Теперь стоит задаться вопросом как nose ищет тесты. По-умолчанию он начинает с поиска файлов с тестами в текущей директории, если не задана другая с помощью ключа —w. В поиске nose руководствуется следующими правилами.

  • Имена директорий, модулей, классов и функций проверяются с помощью специального регулярного выражения testMatch, и все подходящие считаются тестом. Кроме того, все наследники класса unittest.TestCase интерпретируются как тесты, если находятся внутри модуля, считаемого тестом.
  • Директории, не похожие на тесты и не являющиеся пакетами — не проверяются.
  • Пакеты проверяются всегда, но собираются только если они похожи на тест. Это значит что вы можете включить тесты в свой пакет (somepackage/test) и nose использует эти тесты, без запуска лишнего кода пакета.
  • Если у пакета есть библиотека, и код тестов организован в отдельных директориях, директории библиотеки проверяются первыми.

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

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn

Схожі статті




3 коментарі

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Снова что то с отступами в коде. Может автоэскейпинг? Вопрос: Как можно запускать тесты? Я пробую просто командой nosetests, но если пытаюсь проимпортировать что то из библиотек то получаю ImportErrorFile “/Library/Python/2.5/site-packages/django/conf/init.py”, line 57, in importsettings raise ImportError (“Settings cannot be imported, because environment variable %s is undefined.” % ENVIRONMENT_VARIABLE) ImportError: Settings cannot be imported, because environment variable DJANGO_SETTINGS_MODULE is undefined.

Опять отступы в коде «съелись»

пофиксил. сорри.

Опять отступы в коде «съелись»

Підписатись на коментарі