Unit-тесты на максимум

автор рубрика
Unit-тесты на максимум

Сегодня продолжим разговор об автоматизированном модульном тестировании на Python. В этой статье поговорим о дополнительных возможностях unit-тестов стандартной библиотеки unittest: запуск и организация отдельных тестов, игнорирование и выполнение ожидаемых на провал тестов.

Запуск отдельных тестов и детальная информация

В прошлый раз Мы упоминали, что запускать тесты можно просто, исполняя Python-файлы в командной строке, если вызывается функция unittest.main. Помимо этого, есть возможность запускать тесты по отдельности (блок __main__ убирать не нужно):

python -m unittest test1 test2 # разные файлы с тестами
python -m unittest test1.TestClass # тест класса
python -m unittest test1.TestClass.test_method # только метод

Чтобы получить более детальную информацию о проведении тестов, указывается флаг -v:

python -m unittest -v test_module

Рыскаем по тестам

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

python -m unittest
python -m unittest discover # то же самое

Опция discover может иметь такие флаги, как:

  • -v, --verbose детальная информация
  • -s, --start-directory директория, с которой начать рыскать по тестам
  • -p, --pattern шаблон для поиска (по умолчанию test*.py)

Флаги -s, -p могут быть опущены, если аргументы указаны по порядку. Следующие две строчки эквивалентны:

python -m unittest discover -s project_directory -p "*_test.py"
python -m unittest discover project_directory "*_test.py"

Организация тестов

Мы уже говорили, что методы setUp и tearDown выполняются каждый раз при проведении теста. Также в библиотеке unittest есть setUpClass, который выполняется до того, как запустится класс с тестами; метод tearDownClass выполняется после выполнения всех тестов. Оба метода должны быть задекорированы @classmethod. Например, вот так выглядит код на Python c инициализацией экземпляров тестируемого класса:

@classmethod
def setUpClass(cls):
    cls.p = Person("Vasya", "Frolov")

Рекомендуется группировать тесты в соответствии с тестируемыми особенностями с помощью класса TestCase из unittest. В большинстве случаев достаточно использовать unittest.main, но если требуется полный контроль над тестами, то можно написать следующее:

def suite():
    suite = unittest.TestSuite()
    suite.addTest(TestPerson('test_email'))
    suite.addTest(TestPerson('test_fullname'))
    return suite

if __name__ == '__main__':
    runner = unittest.TextTestRunner()
    runner.run(suite())

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

Игнорирование тестов и ожидаемые провалы

Устаревшие и неправильные тесты могут быть проигнорированы. Их можно маркировать, добавив сообщение о неисправности кода. Для этого используется декоратор skip. Ниже показаны примеры на Python c декораторами:

  • skip – полностью игнорирует,
  • skipIf – игнорирует, если условие выполняется,
  • skipUnless – игнорирует, если условие не выполняется.
class MyTestCase(unittest.TestCase):

    @unittest.skip("demonstrating skipping")
    def test_nothing(self):
        pass

    @unittest.skipIf(mylib.__version__ < (1, 3),
                     "not supported in this library version")
    def test_format(self):
        # Tests that work for only a certain version of the library.
        pass

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

Результат может быть выведен с использование флага -v:

test_format (__main__.MyTestCase) ... skipped 'not supported in this library version'

test_nothing (__main__.MyTestCase) ... skipped 'demonstrating skipping'

test_windows_support (__main__.MyTestCase) ... skipped 'requires Windows'

Ожидаемые unit-тесты на провал можно реализовать с помощью декоратора expectedFailure:

class ExpectedFailureTestCase(unittest.TestCase):
    @unittest.expectedFailure
        def test_fail(self):
            self.assertEqual(1, 0, "broken")

Подтесты

Если требуется проверить модуль на определенные значения, то пригодиться контекстный менеджер subTest. Например, тест с проверкой на четность:

class NumbersTest(unittest.TestCase):

    def test_even(self):
        """
        Тест проверяет, что числа от 0 до 5 четные.
        """
        for i in range(0, 6):
            with self.subTest(i=i):
                self.assertEqual(i % 2, 0)

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

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
...
AssertionError: 1 != 0
 
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
...
AssertionError: 1 != 0

Как видим, он показывает, что при нечетных значениях подтест проваливается. Без использования подтестов выполнение тут же бы закончилось при первом несоответствии. Кроме того, выводится значение i, что очень удобно при отладке.

 

Как применять модульное тестирование для своих проектов Data Science, вы узнаете на наших практических курсах по Python в лицензированном учебном центре обучения и повышения квалификации IT-специалистов в Москве.

Комментировать

Ваш адрес email не будет опубликован. Обязательные поля помечены *