Я начал изучать Selenium, и мне любопытно, как реализовать поведение класса PythonOrgSearch, который наследуется от unittest.TestCase. А именно, каждый метод, который начинается с test_, будет вызываться автоматически после инициализации. Я знаю, что это поведение реализовано в TestCase, но мне интересно, как сделать что-то похожее. Есть ли шаблон дизайна, который позаботится об этом?

И еще один бонусный вопрос, какой смысл assert True, так как условие всегда True

import unittest
from selenium import webdriver


class PythonOrgSearch(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.Chrome("C:\chorme\chromedriver.exe")
        self.driver.get("http://www.python.org")

    def test_example(self):
        print("Test")
        assert True

    def not_test(self):
        print("Not a test")

    def tearDown(self):
        self.driver.close()


if __name__ == "__main__":
    unittest.main()
0
kaktus_car 28 Июл 2020 в 17:41

3 ответа

Лучший ответ

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

Ниже приведен пример его применения к тому, что вы хотите сделать. Метод метакласса __new__() просматривает содержимое определяемого класса - когда он вызывается - и ищет вызываемые атрибуты, имена которых начинаются с test_. После этого он определяет методы __init__() и post_init() и делает их частью класса. Первый вызывает последний метод, который затем итеративно вызывает все определенные методы, которые имеют совпадающие имена.

class MyMetaClass(type):
    """ Create class that calls an added post_init() method which in turn calls
        all method's whose names start with "test_".
    """
    def __new__(meta, classname, bases, classdict):

        # Get any class __init__() method defined.
        class_init = classdict.get('__init__', lambda *_, **__: None)

        test_funcs = [value for key, value in classdict.items()
                        if key.startswith('test_') and callable(value)]

        def __init__(self, *args, **kwargs):
            print('In metaclass generated __init__()')
            class_init(self, *args, **kwargs)  # Call class' __init__() method.
            self.post_init()

        def post_init(self):
            print('In metaclass generated post_init()')
            for method in test_funcs:
                print(f'calling {classname}.{method.__name__}()')
                method(self)

        classdict.update({'__init__': __init__,  # Attach methods to class.
                          'post_init': post_init})

        return type.__new__(meta, classname, bases, classdict)



class Example(metaclass=MyMetaClass):

    def __init__(self, arg, macnab=None):
        print(f'in Example.__init__({arg!r}, macnab={macnab!r})')

    def setUp(self):
        pass

    def test_example1(self):
        print("Test1")

    def test_example2(self):
        print("Test2")

    def not_test(self):
        print("Not a test")

    def tearDown(self):
        print("Also not a test")
        pass


print('Creating instance of Example')
Example = Example(42, macnab='keyword')

Выход:

Creating instance of Example
In metaclass generated __init__()
in Example.__init__(42, macnab='keyword')
In metaclass generated post_init()
calling Example.test_example1()
Test1
calling Example.test_example2()
Test2
1
martineau 28 Июл 2020 в 21:32

По первому вопросу вы можете использовать dir() в self, чтобы получить список его членов (Полезная документация для dir).

После этого вы можете протестировать шаблон имени простым способом, и, если он вызывается, вы можете вызвать его:

for name in dir(self):
    if name[:5] == 'test_' and callable(getattr(self, name)):
        res = getattr(self, name)()
        print(res)

Что касается вашего бонусного вопроса, то обычно вынуждают перегружать функцию.

1
Zompa 28 Июл 2020 в 14:57

Я бы предположил, что они просто находят вызываемые методы, которые начинаются с «test_» с использованием функции dir(). То, что вы могли бы достичь довольно легко, как:

class CustomTestCaseRunner:
    def run(self):
        methods = [
            m for m in dir(self)
            if callable(getattr(self, m))
            and m.startswith("test_")
        ]

        for m in methods:
            print(f"Running {self.__class__.__name__}.{m}")
            getattr(self, m)()

class MyTest(CustomTestCaseRunner):
    def test_foo(self):
        assert True

    def test_bar(self):
        assert 1


MyTest().run()
# Running MyTest.test_bar
# Running MyTest.test_foo

Что касается вашего второго вопроса о assert True, маловероятно, что вы когда-либо действительно assert True в реальном коде. Эта функция, кажется, просто пример. assert обычно используется в ответе от функции. Вот несколько примеров:

assert isinstance(1, int)
assert isinstance("foo", str)

Когда условие оценивается как False, оно вызовет AssertionError, что не пройдёт ваш тестовый пример.

1
Tgsmith61591 28 Июл 2020 в 15:03