根据xsd模式符合xml元素的单元测试函数



说,为了简单起见,我们有三个元素"a"b";以及";c";在xsd中定义,其中:

  • "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";可以成为自己的孩子。

我的一些主观想法:

  • 根据您所展示的内容,make_element_a()应该只是一个正则函数。它没有理由成为固定节目。

  • 既然您的所有函数都在XML元素上操作,为什么不将测试参数设置为XML片段呢?您可以编写一个简单的助手函数来将这些片段解析为元素,这将适应"的测试;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片段,而多行字符串确实会使参数列表难以读取。我写了一个名为Parametrize From File的包,它可以通过(顾名思义(允许您在外部文件中指定参数来帮助实现这一点。支持所有常见的配置文件格式(YAML、TOML、JSON(,但在我看来,多行字符串的最佳格式是嵌套文本。

以下是使用这种方法进行测试的情况:

# 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)

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

最新更新