Пользовательский поиск класса элемента не загружает пользовательский элемент - PullRequest
2 голосов
/ 01 мая 2020

Я реализую тесты для некоторых lxml пользовательских элементов (например, ParentElement, ChildElement), которые зарегистрированы через декоратор из пользовательского класса поиска (ModelLookup).

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'

На С другой стороны, если объекты создаются как часть тестовой функции, все работает нормально.

В идеале я хотел бы повторно использовать эти приборы в других тестах, поэтому сначала определю их в контесте.

Код модуля:

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 ]

2 голосов
/ 01 мая 2020

Вы не можете использовать подобный пользовательский поиск. Поиск применяется только при анализе дерева документа XML из источника. Таким образом, вы вообще не вызываете пользовательский поиск в своих тестах, никогда не регистрируете пользовательские типы элементов. Остальная проблема, с которой вы сталкиваетесь, - это просто сборка мусора между двумя вызовами функций (результат работы, потому что вы не выходите за рамки функции фикстуры). Когда при анализе документа применяется поиск, все работает как положено:

@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()
    ...
1 голос
/ 01 мая 2020

Это не проблема приспособлений pytest. Та же самая ошибка происходит, когда simple_family является нормальной функцией.

Ух ты, похоже, у тебя проблема с управлением памятью: O. Если вы возвращаете все объекты из simple_family return adam, eclios, ruby или используете yield, все работает нормально. Если вы не используете yield:

ruby is not simple_family.child_element[0]

Выглядит как серьезная ошибка в библиотеке.

РЕДАКТИРОВАТЬ: Это также хорошо работает, если ruby и ecl ios глобальные переменные: D

0 голосов
/ 01 мая 2020

Я нашел рабочее решение, но не совсем понимаю, почему оно работает.

Если я заменим 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
...