【问题标题】:Unit-test functions that conform xml elements according to a xsd schema根据 xsd 模式符合 xml 元素的单元测试函数
【发布时间】:2022-01-21 12:59:01
【问题描述】:

为了简单起见,我们在 xsd 中定义了三个元素“a”、“b”和“c”,其中:

  • “a”和“b”是“c”的孩子,
  • 元素“c”也可以是其自身的子元素,
  • 元素“a”和“b”没有子元素。

我编写了一个“扫描”xml 的函数,如果它检测到错误的元素,它会尝试 - 如果可能的话 - 根据 xsd 使它们符合。主要代码:

# conformity.py

from lxml.etree import Element

def conform_element_a(root: Element) -> Element | None:
   # If some attributes of the root as element "a" 
   # are not compliant, apply some transformations
   # to these in order to conform root.
   # If transformations are possible, return root.
   # If transformations are not possible, return None.

def conform_element_b(root: Element) -> Element | None:
   # same as before

def conform_element_c(root: Element):
   # conform child "a"
   a = root.find("a")
   if a is not None:
      a = conform_element_a(a)
      if a is None:
         root.remove(a)
   # conform child "b"
   b = root.find("b")
   if b is not None:
      b = conform_element_b(b)
      if b is None:
         root.remove(b)
   # conform child "c"
   c = root.find("c")
   if c is not None:
      c = conform_element_c(c)

现在,我想测试这三个功能。为了测试conform_element_aconform_element_b,我正在考虑使用工厂作为固定装置,如下conform_element_a

import pytest
from lxml import etree
from mypackage.utils import tree_equality
from mypackage.conformity import conform_element_a

# Factory
@pytest.fixture
def make_element_a():
    def _make_element_a(**kwargs):
        # build an element "a"
        # according to kwargs
        return a

    return _make_element_a


class TestConformA:
    @pytest.mark.parametrize("attrib_1", [...])
    @pytest.mark.parametrize("attrib_2", [...])
    def test_none_case(make_element_a, attrib_1, attrib_2):
        a = make_element_a(attrib_1, attrib_2)
        a = conform_element_a(a)
        assert a is None
    
    @pytest.mark.parametrize("attrib_1, exp1", [...])
    @pytest.mark.parametrize("attrib_2, exp2", [...])
    def test_not_none(make_element, attrib_1, attrib_2, exp1, exp2):
        a = make_element_a(attrib_1, attrib_2)
        expected = make_element_a(exp1, exp2)
        a = conform_element_a(a)
        assert tree_equality(a, expected)

两个问题:

  1. 我想知道这不是测试conform_element_aconform_element_b 的好方法。这里我放了两个属性作为示例,但实际上还有更多,而且很快就会变得冗长。

  2. 我看不到如何测试函数conform_element_c。以前,只有属性作为参数。但是现在有属性和孩子!尤其是“c”可以是它自己的孩子的情况。

【问题讨论】:

    标签: python xml unit-testing xsd pytest


    【解决方案1】:

    我的一些主观想法:

    • 根据您所展示的,make_element_a() 应该只是一个常规函数。它没有理由成为固定装置。

    • 既然您的所有函数都对 XML 元素进行操作,为什么不将您的测试参数设为 XML sn-ps?您可以编写一个简单的辅助函数来将这些 sn-ps 解析为元素,这将适应“a”、“b”和“c”的测试。

    • 我认为嵌套@pytest.mark.parametrize 这样的装饰器不是一个好主意。这样做很难将预期输入与预期输出明确关联起来。我唯一会这样做是当某些参数与预期输出无关时,例如测试应该构造相同对象的多个工厂。

    考虑到以上内容,我可以这样编写这些测试:

    import pytest
    from mypackage.utils import elem_from_str, tree_equality
    from mypackage.conformity import conform_element_a
    
    @pytest.mark.parametrize(
        'given, expected', [
            (
                '<a/>',
                '<a/>',
            ), (
                '<a x=1/>',
                '<a x=2/>',
            ), (
                '<a y=1/>',
                None,
            ),
        ]
    )
    def test_conform_element_a(given, expected):
        given = elem_from_str(given)
        expected = elem_from_str(expected)
        conformed = conform_element_a(given)
        assert tree_equality(conformed, expected)
    

    使用 XML 对测试参数进行编码的一个缺点是您最终可能会得到多行 XML sn-ps,而且多行字符串确实使参数列表难以阅读。我编写了一个名为Parametrize From File 的包,它可以通过(顾名思义)允许您在外部文件中指定参数来帮助解决这个问题。支持所有常见的配置文件格式(YAML、TOML、JSON),但我认为多行字符串的最佳格式是Nested Text

    以下是使用这种方法的测试结果:

    # test_conform_element.nt
    test_conform_element_c:
      -
        id: no-children
        given: <c/>
        expected: <c/>
      -
        id: child-ab
        given:
          > <c>
          >  <a/>
          >  <b/>
          > </c>
        expected:
          > <c>
          >  <a/>
          >  <b/>
          > </c>
      -
        id: child-c-recursive
        given:
          > <c>
          >  <c>
          >   <a>
          >   <b>
          >  </c>
          > </c>
        expected:
          > <c>
          >  <c>
          >   <a>
          >   <b>
          >  </c>
          > </c>
    
    # test_conform_element.py
    import parametrize_from_file
    from mypackage.utils import elem_from_str, tree_equality
    from mypackage.conformity import conform_element_c
    
    @parametrize_from_file
    def test_conform_element_c(given, expected):
        given = elem_from_str(given)
        expected = elem_from_str(expected)
        conformed = conform_element_c(given)
        assert tree_equality(conformed, expected)
    

    我认为这使得测试代码和测试参数更容易阅读。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多