Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

python unittest decorators. Условный декоратор не проверяет условие непосредственно перед выполнением тест метода

unittest
python
Теги: #<Tag:0x00007f7b6902e338> #<Tag:0x00007f7b6902e130>

(Евгений Бухгаммер) #1

Добрый день! столкнулся с такой необходимстью: в модуле unittest в Питоне есть отличные декораторы, которые, однако, строят структуру тестов в момент look up’а текущего модуля. Иначе не объяснить, почему если я в main блоке перед запуском unittest.main() переопределяю переменную, которая решает, будет ли запущен тест - в итоге не влияет на результат, а тест скипается или выполняется в зависимости от значения ключа словаря, объявленно ДО непосредственно моего тестового класса. Пример кода:

import argparse
import unittest
import sys

default_settings = {}
default_settings['license_option'] = False

class MyTest(unittest.TestCase):
    
    @unittest.skipIf(not default_settings['licence_option'], "License check is omited")
    def test_a(self):
        self.assertTrue(False)



if __name__ == '__main__':
    
    parser = argparse.ArgumentParser(description='Parser for CLI arguments for BVT')
    
    parser.add_argument('--license-check', action='store_true', help='provide the key', default=False)
    
    args = parser.parse_args(['--license-check'])

    if args.license_check:
        default_settings['license_option'] = True
    else:
        default_settings['license_option'] = False
    unittest.main()

Можно ли как то обойтись без “грязного” на мой взгляд хака: если проверку скипа теста делать в самом тестовом методе.В это случае тест не будет скипаться в момент пробегания интерпретатором по всему модулю, а пробежит непосредственно уже в него “заглянув” в самом рантайме. Такое решение мне кажется некрасивым. Неужели чтобы декоратор unittest.skipIf работал, мне нужно однозначно вычислить, пропускать тест или нет ДО объявления своего тест метода? И, следовательно, переместить весь мой тест класс вниз в main block уже после прочтения всех коммандлайновых аргументов?

Есть ли какие либо советы, как делать правильно?


(Oleksandr Khotemskyi) #2

Ну для начала нужно поправить имя ключа в skipIf -
default_settings[‘licen se_option’] = False
@unittest.skipIf(not default_settings[‘licen ce_option’], “License check is omited”)

Ты прав - unittest отмечает тест для пропуска еще до выполнения блока if name == ‘main
Это особенность работы декораторов и unittest
Потому сколько в нем ты не меняй на True - тест уже не запустится.
Какой я вижу выход - парсить аргументы до того как отметить тест для пропуска. Вот смотри -

import argparse
import unittest

parser = argparse.ArgumentParser(description='Parser for CLI arguments for BVT')
parser.add_argument('--license-check', action='store_true', help='provide the key', default=False)
args = parser.parse_args(['--license-check'])
default_settings = {'license_option': args.license_check}

class MyTest(unittest.TestCase):

    @unittest.skipUnless(default_settings['license_option'], "License check is omited")
    def test_a(self):
        print 'inside test'
        self.assertTrue(False)


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

Если параметр --license-check передается не всегда - то это тоже нужно обрабатывать.


(Евгений Бухгаммер) #3

Спасибо за ответ.

Действительно, передо мной стоит 2 выбора: вынести логику парсинга из мейн блока, что на мой взгляд не совсем хорошо. Либо отказаться от декоратора и проверить уже внутри тест метода ( к тому времени по ссылке на переменную будет лежать другое, обновленное значение ).

Если первый случай на мой взгляд перечит хорошей pythonic практике, что любой скрипт должен быть написан с идеей, что это библиотека ( импорты, объявления функций и классов в общем скоупе модуля, а тестовая логика, переменные разного рода - в main блоке ).

То второй случай заставит читать код, вместо того, чтобы сразу понять, почему он пропустился. Декораторы воспринять проще, чем глазами смотреть в тестовый метод, чтобы (хоть и в самом начале метода, но все же) увидеть условие на пропуск теста.

Наверное, я все же выберу второй вариант :slight_smile:


(Oleksandr Khotemskyi) #4

Можно сделать 2 мейн блока. Тогда будет больше похоже на библиотеку. if name == ‘main’ блок ведь нужен только для того чтобы выполнить кусок кода только если модуль был запущен напрямую, а не импортом из другого модуля.

import argparse
import unittest
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Parser for CLI arguments for BVT')
    parser.add_argument('--license-check', action='store_true', help='provide the key', default=False)
    args = parser.parse_args(['--license-check'])
    default_settings = {'license_option': args.license_check}

class MyTest(unittest.TestCase):

    @unittest.skipUnless(default_settings['license_option'], "License check is omited")
    def test_a(self):
        print 'inside test'
        self.assertTrue(False)


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

(Евгений Бухгаммер) #5

Отлично! такое простое решение и в то же время понятное и не усложняющее код. Я почему-то привык и всегда считал, что не импортируемую часть кода лучше располагать в “футере” скрипта, но ведь его можно расположить дополнительно и в другом месте, в данном случае сразу после импортов будет удобно и не затеряется в коде.