Сегодня продолжим разговор об автоматизированном модульном тестировании на 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-специалистов в Москве.