Правильно ли я вызываю приборы pytest, вызывая их только для функции в классе?

ОБНОВИТЬ:

Когда я комментирую все остальные тесты, test_add_two_same_interfaces(self,default_router,default_interface работает как положено.

Когда я запускаю все тесты вместе, последний терпит неудачу, но остальные проходят. Является ли это проблемой, что приборы pytest не созданы, чтобы быть независимыми друг от друга? Означает ли это, что я должен передать маршрутизатор и интерфейс, а затем удалить объект в конце?

@pytest.fixture(scope='module')
def default_router():
    name = 'default'
    return Router(name)

@pytest.fixture(scope='module')
def default_interface():
    port = 'fa0/1'
    cidr = '192.168.0.0/24'
    ip_address = '192.168.1.1'
    fa01 = Interface(port=port, cidr=cidr, ip_address=ip_address)
    return fa01

class TestInit(object):
    def test_valid_args(self,default_router):
        router = default_router
        name = router.hostname

        hostname_message = f'Expected "default" as hostname, Actual: <{name}>'
        assert router.hostname == 'default',hostname_message

        expected_commands = ["en", "conf t", "no ip domain-lookup"]
        actual_commands   = router.commands
        commands_message  = f'Expected: {expected_commands}\n Actual: <{actual_commands}>'
        assert actual_commands == expected_commands,commands_message

        router_message = "Expected to get the default router"
        assert Router._all_routers[0] == router,router_message

    def test_invalid_args(self,default_router):
        """Test that if you try and make a second Router with the same name, it will fail."""
        router = default_router
        with pytest.raises(ValueError):
            router1 = Router("default")

class TestAddInterface(object):
    def test_valid_args(self,default_router,default_interface):
        router = default_router
        router.add_interface(default_interface)

        expected_interface = 'fa 0/1,192.168.0.0/24,192.168.1.1'
        actual_interface   = str(router.interfaces[0])
        interface_add_message = f'Expected: {expected_interface}\n Actual:'
        assert actual_interface == expected_interface,interface_add_message

    def test_add_two_same_interfaces(self,default_interface,default_router):
        """This test adds the same interface to a Router twice to test that it raises
        a ValueError."""
        print("current directory", os.getcwd())
        router = default_router
        interface = default_interface
        router.add_interface(interface)
        repeat_interface = 'fa0/1,192.168.0.0/24,192.168.1.1'.split(",")
        print(repeat_interface)
        with pytest.raises(ValueError):
            new_copy = Interface(repeat_interface[0], repeat_interface[1], repeat_interface[2])
            print([id(x) for x in router.interfaces])
            print(id(new_copy))
            router.add_interface(new_copy)

СТАРЫЙ:

Я пытаюсь увидеть, вызовет ли класс Router ошибку ValueError, если я попытаюсь добавить к нему два одинаковых интерфейса. Я использую фикстуры для создания маршрутизатора и интерфейса в тестовом модуле под названием test_router.py. Я новичок в использовании API pytest и пытаюсь использовать несколько приборов в одной тестовой функции, которая находится в тестовом классе. Является ли мой вызов @pytest.mark.usefixtures('default_router','default_interface') неправильным над тестом? Я получаю следующее сообщение E AttributeError: 'function' object has no attribute 'add_interface' Класс интерфейса и маршрутизатора:

class Interface(object):
    """
    Interface works with fast ethernet and gigabit ethernet ports.
    TO DO: Provide support for serial interfaces.
    """
    def __init__(self,port:str,cidr:str,ip_address:str):
        # Make sure you can change the interface.
        self.init_commands: [str] = [None, "no switchport"]
        self.config_commands: [str] = [None]
        # Stop the port interface from going down
        # Get out of the interface
        self.exit_commands = ["no shut", "exit"]
        self._port: [str] = None
        self._cidr: [str] = None
        self._ip_address: [str] = None
        # Example 255.255.255.0
        self.netmask: str = None
        self.port = port
        self.cidr = cidr
        self.ip_address = ip_address
        self.init_setup()
        self.config_setup()

    @property
    def commands(self) -> [str]:
        commands = self.init_commands + self.config_commands + self.exit_commands
        return commands

    @property
    def port(self) -> str:
        # A list of valid regex for the first part of the port.
        return ' '.join(self._port)

    @property
    def cidr(self) -> str:
        return '/'.join(self._cidr)

    @property
    def ip_address(self) -> str:
        return '.'.join(self._ip_address)

    @port.setter
    def port(self,port:str):
        # todo: Add support for VLAN(You'll support Switches Later).
        valid_port_types = ['^fastethernet$', '$f$', '^fa$', '^gigabitethernet$', '^g$', '^gb$']
        # Split the port by spaces.
        port_split = port.split()
        # If the port is not split by spaces, then split it by letters and numbers separated by "/".
        if len(port_split) != 2:
            port_split = re.match("([a-zA-Z]+)([0-9]+/[0-9]+)",port_split[0])
            if port_split == None:
                raise ValueError(f"<{port}> is not a valid port name")
            port_split = list(port_split.groups())
        # If the first part of the port is not a valid port type, raise a ValueError.
        if sum([bool(re.match(port_name, port_split[0])) for port_name in valid_port_types]) != 1:
            raise ValueError(f'<{port_split[0]}> is not a valid port name')
        # If the second part of the port does not contain '/', raise a ValueError.
        if '/' not in port_split[1]:
            raise ValueError(f'port number <{port_split[0]}> <{port_split[1]}> does not contain "/"')
        self._port = port_split
        self.init_setup()

    @cidr.setter
    def cidr(self,cidr):
        cidr_split = re.split("/",cidr)
        if len(cidr_split) != 2:
            raise ValueError(f'cidr <{cidr}> does not contain "/" or is invalid')
        cidr_split = [value.strip() for value in cidr_split]
        valid_subnet_address = '\d{,3}.\d{,3}.\d{,3}.\d{,3}'
        if not bool(re.match(valid_subnet_address,cidr_split[0])):
            raise ValueError(f'<{cidr_split[0]}> is not a valid subnet address')
        if '/' in cidr_split[1]:
            cidr_split[1] = cidr_split[1].replace("/","")
        prefix = int(cidr_split[1])
        if prefix < 0 or prefix > 32:
            raise ValueError(f"<{cidr_split[1]}> is not a valid subnet mask")
        network,netmask = self.cidr_to_netmask('/'.join(cidr_split))
        self.netmask = netmask
        self._cidr = [network,str(prefix)]
        if self._ip_address != None:
            self.config_setup()

    @ip_address.setter
    def ip_address(self,ip_address):
        # Remove spaces and split ip address by "."
        split_ip_address = ip_address.replace(" ","").split(".")
        valid_ip_address = '\d{,3}.\d{,3}.\d{,3}.\d{,3}'
        # If the IP address does not match X.X.X.X, raise a ValueError.
        if not bool(re.match(valid_ip_address, ip_address)):
            raise ValueError(f'<{ip_address}> is not a valid IP address')
        self._ip_address = split_ip_address
        self.config_setup()

    @staticmethod
    # https://stackoverflow.com/questions/33750233/convert-cidr-to-subnet-mask-in-python
    # "<<" and ">>" are bitwise operators that move the binary value over by the number of digits.
    def cidr_to_netmask(cidr):
        network, net_bits = cidr.split('/')
        host_bits = 32 - int(net_bits)
        # ntoa only supports 32 bit IPV4, use ntop to support ipv6
        # todo support IPV6
        netmask = socket.inet_ntoa(struct.pack('!I', (1 << 32) - (1 << host_bits)))
        return network, netmask

    def init_setup(self):
        # Enter the interface.
        self.init_commands[0] = "int %s" % self.port


    def config_setup(self):
        self.config_commands = ["ip address %s %s" % (self.ip_address, self.netmask)]

    def __str__(self):
        return ','.join([self.port,self.cidr,self.ip_address])

class Router(metaclass=IterRouter):
    _all_routers = []
    available_protocols = ['RIP1','RIP2','OSPF1','OSPF2']

    def __init__(self,hostname):
        # If the hostname already exists, then raise a ValueError
        double_list = [hostname == router.hostname for router in self._all_routers]
        if sum(double_list) != 0:
            raise ValueError(f"Router hostname <{hostname}> already exists")
        # Add new router to list of instances.
        Router._all_routers.append(self)
        # Set class instance attributes.
        self.hostname = hostname

    def add_interface(self,interface:Interface):
        # If the interface already exists, raise a ValueError.
        if len(self.interfaces) != 0:
            if sum([str(interface) == str(current_interface) for current_interface in self.interfaces]) != 0:
                raise ValueError('This interface already exists')
        self.interfaces.append(interface)

Светильники:

@pytest.fixture(scope='module')
def default_router():
    name = 'default'
    return Router(name)

@pytest.fixture(scope='module')
def default_interface():
    port = 'fa0/1'
    cidr = '192.168.0.0/24'
    ip_address = '192.168.1.1'
    fa01 = Interface(port=port, cidr=cidr, ip_address=ip_address)
    return fa01

Код для тестирования:

class TestAddInterface(object):
    @pytest.mark.usefixtures('default_router','default_interface')
    def test_add_two_same_interfaces(self):
        """This test adds the same interface to a Router twice to test that it raises
        a ValueError."""
        router = default_router
        router.add_interface(default_interface)
        repeat_interface = 'fa0/1,192.168.0.0/24,192.168.1.1'.split(",")
        print(repeat_interface)
        with pytest.raises(ValueError):
            new_copy = Interface(repeat_interface[0], repeat_interface[1], repeat_interface[2])
            print([id(x) for x in router.interfaces])
            print(id(new_copy))
            router.add_interface(new_copy)

Ошибка:

================================== FAILURES ===================================
________________ TestAddInterface.test_add_two_same_interfaces ________________

self = <test_router.TestAddInterface object at 0x000001B437FDE520>

    @pytest.mark.usefixtures('default_router','default_interface')
    def test_add_two_same_interfaces(self):
        """This test adds the same interface to a Router twice to test that it raises
        a ValueError."""
        router = default_router
>       router.add_interface(default_interface)
E       AttributeError: 'function' object has no attribute 'add_interface'

person David Warshawsky    schedule 17.08.2020    source источник


Ответы (1)


Ссылка ниже решила мою проблему. Фабрики как приспособления — это путь, который задумали разработчики. Повторное использование фикстуры pytest в том же тесте

person David Warshawsky    schedule 18.08.2020