我正在为一些lxml
自定义元素(例如ParentElement
,ChildElement
)实施测试,这些自定义元素是通过装饰器从自定义查找类(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)])
感谢您的帮助!
答案 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