自定义元素类查找未加载自定义元素

时间:2020-05-01 15:40:48

标签: python pytest lxml

我正在为一些lxml自定义元素(例如ParentElementChildElement)实施测试,这些自定义元素是通过装饰器从自定义查找类(ModelLookup)中注册的。 / p>

pytest用于运行测试,而我正在使用conftest.py中定义的灯具。

问题在于,当将自定义元素的对象创建为夹具的一部分时,测试功能上的键入会丢失,并且出现以下错误:

>   assert ['eclios', 'ruby'] == sorted([e.name for e in simple_tidy_family.child_element])
E   AttributeError: 'lxml.etree._Element' object has no attribute 'name'

另一方面,如果对象是作为测试功能的一部分创建的,则所有对象运行良好。

理想情况下,我想在其他测试中重复使用这些固定装置,因此首先要在conftest中对其进行定义。

模块代码:

from __future__ import unicode_literals
from lxml import etree


class ModelLookup(etree.PythonElementClassLookup):

    _lookup = {}

    @classmethod
    def register_node_handler_class(cls, handler_cls):
        if handler_cls.cls_tag not in cls._lookup.keys():
            cls._lookup[handler_cls.cls_tag] = handler_cls
        return handler_cls

    def lookup(self, doc, node):
        if node.tag in self._lookup.keys():
            print(node.tag)
            return self._lookup[node.tag]
        return etree.ElementBase


@ModelLookup.register_node_handler_class
class ParentElement(etree.ElementBase):

    cls_tag = 'ParentElement'

    @staticmethod
    def tada():
        return 'tada'

    @property
    def child_element(self):
        return self.xpath('./ChildElement')

    @child_element.setter
    def child_element(self, value):
        self.append(value)

    @property
    def name(self):
        return self.get('name')

    @name.setter
    def name(self, value):
        self.set('name', value)


@ModelLookup.register_node_handler_class
class ChildElement(etree.ElementBase):

    cls_tag = 'ChildElement'

    @property
    def name(self):
        return self.get('name')

    @name.setter
    def name(self, value):
        self.set('name', value)

conftest.py

from __future__ import unicode_literals
import pytest
import xmlpal.xmlpal as xpal
import logging


@pytest.fixture()
def simple_family():

    ruby = xpal.ChildElement(**{'name': 'ruby'})
    eclios = xpal.ChildElement(**{'name': 'eclios'})

    adam = xpal.ParentElement()
    adam.append(ruby)
    adam.append(eclios)

    logging.info(f'my name is {ruby.name}')

    return adam

测试代码:

from __future__ import unicode_literals
from lxml import etree
import xmlpal.xmlpal as xpal


def test_fixture_family(simple_family):

    assert 'tada' == simple_family.tada()
    assert isinstance(simple_family, xpal.ParentElement)
    assert 2 == len(simple_family.child_element)
    assert 2 == len([e for e in simple_family.child_element if isinstance(e, etree._Element)])
    assert ['eclios', 'ruby'] == sorted([e.name for e in simple_family.child_element])
    assert 2 == len([e for e in simple_family.child_element if isinstance(e, xpal.ChildElement)])


def test_local_family():

    ruby = xpal.ChildElement(**{'name': 'ruby'})
    eclios = xpal.ChildElement(**{'name': 'eclios'})

    adam = xpal.ParentElement()
    adam.append(ruby)
    adam.append(eclios)

    assert 'tada' == adam.tada()
    assert isinstance(adam, xpal.ParentElement)
    assert 2 == len(adam.child_element)
    assert 2 == len([e for e in adam.child_element if isinstance(e, etree._Element)])
    assert ['eclios', 'ruby'] == sorted([e.name for e in adam.child_element])
    assert 2 == len([e for e in adam.child_element if isinstance(e, xpal.ChildElement)])

感谢您的帮助!

3 个答案:

答案 0 :(得分:1)

这不是pytest固定装置的问题。当simple_family是正常函数时,会发生相同的错误。

哇,看来您有内存管理问题:O。如果您从simple_family return adam, eclios, ruby返回所有对象或使用yield,则一切正常。如果您不使用yield

ruby is not simple_family.child_element[0]

看起来像库中的一些严重错误。

编辑:如果ruby和eclios是全局变量:D

答案 1 :(得分:1)

您不能使用这样的自定义查找。仅当从源解析XML文档树时才应用查找。因此,您根本不会在测试中调用自定义查找,永远不要注册您的自定义元素类型。您遇到的其余问题只是两个函数调用之间的垃圾回收(之所以起作用,是因为您没有退出Fixture函数的作用域)。但是,当将查找应用于解析文档时,一切都会按预期进行:

@pytest.fixture
def source():
    """Construct the tree and serialize it to string."""
    ruby = xpal.ChildElement(**{'name': 'ruby'})
    eclios = xpal.ChildElement(**{'name': 'eclios'})

    adam = xpal.ParentElement()
    adam.append(ruby)
    adam.append(eclios)

    return etree.tostring(adam)


@pytest.fixture
def tree(source):
    """Parse the tree from a string using a parser with custom lookup registered."""
    parser = etree.XMLParser()
    parser.set_element_class_lookup(xpal.ModelLookup())
    return etree.fromstring(source, parser)


def test_fixture_family(tree):
    adam = tree
    assert 'tada' == adam.tada()
    ...

答案 2 :(得分:0)

我找到了一个可行的解决方案,但不太了解它为什么起作用。

如果我将灯具中的return替换为yield,则一切正常。

@pytest.fixture()
def simple_tidy_family():

    ruby = xpal.ChildElement()
    ruby.name = 'ruby'
    eclios = xpal.ChildElement()
    eclios.name = 'eclios'

    adam = xpal.ParentElement()
    adam.append(ruby)
    adam.append(eclios)

    yield adam