pytest插件,将预期输出卸载到数据文件
项目描述
pytest-golden
pytest插件,将预期输出卸载到数据文件
简要使用说明
(另请参阅: example/)
pip install pytest-golden
创建测试文件(例如 tests/test_foo.py)
@pytest.mark.golden_test("test_bar/*.yml")
def test_bar(golden):
assert foo.bar(golden["input"]) == golden.out["output"]
通配符选择“黄金”文件,这些文件既是测试的输入也是预期输出。测试基本上是基于文件参数化的。
创建一个或多个这样的YAML文件(例如 tests/test_bar/basic.yml)
input: Abc
output: Nop
运行pytest
来执行测试。
当测试下的函数发生变化时,其结果也可能发生变化,测试将无法通过。您可以运行pytest --update-goldens
来自动重新填充输出。
请参阅详细使用说明。
黄金测试的案例
考虑测试一个函数时的正常情况(例如,一个列出句子中所有单词的函数)。
foo.py
def find_words(text: str) -> list:
return text.split()
tests/test_foo.py
from foo import find_words
def test_find_words():
assert find_words("If at first you don't succeed, try, try again.") == [
"If", "at", "first", "you", "don't", "succeed,", "try,", "try", "again."
]
您为该函数编写了基本的测试,但手动编写期望的输出可能相当繁琐,尤其是当输出内容较多时。有时,您可能首先编写一个模拟测试,然后从失败消息中复制实际输出。这并没有什么问题,因为这样您仍然会手动检查新的输出是否正确。
使用黄金测试
但让我们使用“黄金测试”重写这个测试。
tests/test_foo.py
from foo import find_words
def test_find_words(golden):
golden = golden.open("test_find_words/test_basic.yml")
assert find_words(golden["input"]) == golden.out["output"]
这里 golden["xxx"]
将直接从相关文件读取一个值。让我们创建这个(YAML)文件
tests/test_find_words/test_basic.yml
input: |-
If at first you don't succeed, try, try again.
与输入不同,golden.out["yyy"]
的工作方式略有不同。通常它也将是测试的输入,从文件(断言将是一个完全正常的 pytest 断言)中获取,但在特殊“更新”模式下,它将接受运行时生成的任何结果并将其放回“黄金”文件。更新文件和最初填充文件都是通过命令 pytest --update-goldens
自动完成的。
tests/test_find_words/test_basic.yml
input: |-
If at first you don't succeed, try, try again.
output:
- If
- at
- first
- you
- don't
- succeed,
- try,
- try
- again.
现在,当仅运行 pytest
时,测试将始终断言结果与预期输出完全相同。这正是单元测试的工作方式。
现在,您可以将其添加到源控制系统中。
引入更改
假设您对标点符号与单词混合在一起的情况不满意,因此您为该函数设计了不同的实现。
foo.py
import re
def find_words(text: str) -> list:
return re.findall(r"\w+", text)
您还希望为其添加另一个测试用例
tests/test_find_words/test_quotation.yml
input: |-
Dr. King said, 'I have a dream.'
output:
- Dr
- King
- said
- I
- have
- a
- dream
让我们将其转换为 参数化 的黄金测试(为每个匹配通配符的文件生成一个测试用例)
tests/test_foo.py
import pytest
from foo import find_words
@pytest.mark.golden_test("test_find_words/*.yml")
def test_find_words(golden):
assert find_words(golden["input"]) == golden.out["output"]
现在,如果我们运行 pytest -v
,我们会看到新的测试一切正常,它被识别为 test_find_words[test_quotation.yml]
,但是代码更改也导致之前的测试现在不一致!您将收到一个正常的 pytest 失败消息。
在这种情况下,您通常会回到测试文件并编辑期望的输出(如果您确实期望它发生变化)。但使用这种方式,您只需运行 pytest --update-goldens
,您会看到“黄金”文件会自动更新(没有测试失败)。生成的差异可以在源控制系统中查看。
--- a/tests/test_find_words/test_basic.yml
+++ b/tests/test_find_words/test_basic.yml
@@ -5,8 +5,9 @@ output:
- at
- first
- you
-- don't
-- succeed,
-- try,
+- don
+- t
+- succeed
- try
-- again.
+- try
+- again
现在,您可以(以及潜在的代码审查人员)决定这个差异是否可以接受,或者是否需要更多更改。您可以再次迭代代码,单元测试也会随着您的迭代而更新,您永远不需要手动编辑它——只需视觉检查更改并进行提交。
用法
golden
修复
将 golden
参数添加到您的 pytest 测试函数中,它将传递一个 GoldenTestFixtureFactory
。
类 GoldenTestFixtureFactory
golden.open(path) -> GoldenTestFixture
在 golden
对象上调用此方法以获取一个实际可用的 修复。
参数 path
是一个路径,相对于调用 Python 测试文件。在测试函数结束时自动进行清理。
@pytest.mark.golden_test(*patterns: str)
使用此装饰器来
- 避免调用
.open
并直接将 适当的修复 作为测试函数的golden
参数,并 - 将参数化添加到“黄金”测试中。
patterns
是一个或多个相对于调用 Python 测试文件的 glob 模式。将为每个匹配的文件创建一个测试。
类 GoldenTestFixture
golden[input_key: str] -> Any
从相关的 YAML 文件中获取一个值,在顶层键中。可能会引发 KeyError
。
golden.get(input_key: str) -> Optional[Any]
与此类似,但如果键不存在,则返回 None
。
golden.out[output_key: str] -> Any
-
在正常模式下
从相关的 YAML 文件中获取一个值,在顶层键中。可能会引发
KeyError
。 -
如果传递了
--update-goldens
标志获取用于键的代理对象,当进行比较(并随后断言)时,标记“黄金”文件应该为此顶级键获取更新值。这些更新在测试套件拆除时执行:原始文件总是重写一次。
golden.out.get(output_key: str) -> Optional[Any]
与此类似,但当与 None
进行比较时,将键标记为已从文件中删除,而不仅仅是具有 None
的值。
如何...
创建一个在 YAML 中可表示的自定义类型
我们将让这些类型被底层实现所知 - ruamel.yaml,但让我们只使用模块 pytest_golden.yaml
提供的透传函数。最好在 conftest.py 中全局应用。
import pytest_golden.yaml
pytest_golden.yaml.register_class(MyClass)
(并参见 ruamel.yaml 的详细信息)
如果您的类等同于单个值,则的备用示例
class MyClass:
def __init__(self, value: str):
self.value = value
pytest_golden.yaml.add_representer(MyClass, lambda dumper, data: dumper.represent_scalar("!MyClass", data.value))
pytest_golden.yaml.add_constructor('!MyClass', lambda loader, node: MyClass(node.value))
或者在继承标准类型的特定情况下,您完全可以完全省略标签,并依赖于与基类型的相等性。
class MyClass(str):
pass
pytest_golden.yaml.add_representer(MyClass, lambda dumper, data: dumper.represent_str(data))
为模块中的所有测试应用默认的黄金文件
考虑这个只使用 pytest_golden
存储输出的测试
注意:即使这些 *.yml
文件是空的,也需要手动创建它们。
def test_foo(golden):
golden = golden.open("stuff/test_foo.yml")
assert foo() == golden.out["output"]
def test_bar(golden):
golden = golden.open("stuff/test_bar.yml")
assert bar("a", "b") == golden.out["output"]
测试体是不同的(因此通过 mark
应用模式是不适用的),但我们仍然希望自动分配黄金文件,而无需重复。
为此,我们可以像这样增强 golden
仪器
@pytest.fixture
def my_golden(request, golden):
return golden.open(f"stuff/{request.node.name}.yml")
def test_foo(my_golden):
assert foo() == my_golden.out["output"]
def test_bar(my_golden):
assert bar("a", "b") == my_golden.out["output"]
在这里,YAML 文件的名称基于测试名称。以前文件名是手动确保匹配的。所以这两个片段是完全等价的。
请注意,您甚至不需要想出一个像 my_golden
这样的单独名称,只需覆盖整个模块的原始 golden
仪器即可。
请参阅 此示例的真实示例。
项目详情
下载文件
下载适合您平台的文件。如果您不确定要选择哪一个,请了解更多关于 安装包 的信息。
源分布
构建分布
pytest-golden-0.2.2.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 54e6f317a533758e6dcc96e6ef9457c610ae1c9db53575686a303f3ef7ad1e35 |
|
MD5 | 529e8da51524ee487e3765759b3a596c |
|
BLAKE2b-256 | c98b111d6b9b2adccb22155f00349148a1cbecd5ed25202b4e4780a2216dcc66 |