关注开源优测不迷路
JavaScript 对象表示法(JSON)可以说是网络上最流行的数据交换格式之一。
Web 服务使用序列化技术将数据从底层数据结构转换为 JSON 格式,以便接收服务能够轻松地对其进行反序列化。
在编写单元测试时,测试 JSON 输入和输出的需求至关重要。
测试数据、API 响应,有时甚至配置文件都是用 JSON 定义的,这使得使用 Pytest 理解如何读取和写入 JSON 变得十分必要。
在本文中,我们将探索在 Pytest 中读取 JSON 数据的 5 种简单方法,这样你就可以用它来验证输入的测试数据、配置文件、API 响应等等。
目标
在本文结束时,你应该能够:
使用 5 种不同的方法在 Pytest 中解析 JSON 数据。
理解如何使用 Pytest 的 fixture(夹具)和参数将 JSON 数据解析到单元测试中。
入门
在开始操作前,请按以下目录组织你的代码结构
本文中不需要特殊的库,只需要 pytest 和一个代码格式化检查工具。
现在让我们来看看在 Pytest 中解析 JSON 输入的 5 种不同方法。
源代码
我们的源代码是一个非常简单的示例,它接受一个 JSON 字符串输入,并根据 “age” 字段计算出 “birth_year”(出生年份)。
read_json/core.py
import json def compute_birth_year(input_data: str): # 将JSON数据解析为Python字典 data = json.loads(input_data) # 计算出生年份 birth_year = 2023 - data["age"] return f"{data['name']} was born in {birth_year}"
让我们看看如何用5种不同的方式来测试这个函数。
在测试中定义 JSON
测试这个函数最简单的方法之一是在我们的单元测试中定义输入的 JSON。
tests/unit/test_read_json.py
def test_compute_birth_year_1(): input_data = '{"name": "John", "age": 30, "city": "New York"}' expected = "John was born in 1993" assert compute_birth_year(input_data) == expected
虽然这种方法很简单,但灵活性较差。
如果你想用新的 JSON 数据运行测试,就必须修改测试模块(这很不方便)。
2. 读取外部 JSON 文件
一种有趣且方便的读取 JSON 文件的方法是将其单独保存,放在测试模块之外。
这样你就可以动态地对其进行修改,而无需更改测试本身。
tests/unit/test_read_json.py
def test_compute_birth_year_2(): file = pathlib.Path("tests/unit/test_data/input1.json") with open(file) as f: input_data = f.read() expected = "Jerry was born in 1968" assert compute_birth_year(input_data) == expected
在这里,我们使用了 pathlib 模块来解析文件,但你也可以使用 json 模块。
这种方法的好处是我们将输入与测试模块解耦了,但如果输入文件不可用,你就会遇到测试错误。
3. 将 JSON 数据作为 fixture 提供
另一种方法(也是我非常喜欢的一种)是将输入数据作为 Pytest 的 fixture 提供。
如果你不熟悉 Pytest 的 fixture 及其工作原理,这篇文章提供了很好的基础介绍。
简而言之,fixture 是 Pytest 函数(通常在 conftest.py 中定义),可以在一个或多个测试模块中轻松使用,并通过 scope 参数进行状态控制。
我们定义 JSON 的 fixture:
tests/unit/conftest.py
@pytest.fixture(scope="session") def input_json(): return json.dumps({ "name": "Eric", "age": 32, "city": "London" })
现在我们可以在单元测试中使用它了。
tests/unit/test_read_json.py
def test_compute_birth_year_3(input_json): expected = "Eric was born in 1991" assert compute_birth_year(input_json) == expected
4. 使用 Pytest 参数定义 JSON
你也可以利用 Pytest 中的参数来定义测试输入。
为此,我们使用 @pytest.mark.parametrize 标记。让我们来看一下。
tests/unit/test_read_json.py
@pytest.mark.parametrize( "input_data, expected", [('{"name": "Robin", "age": 55, "city": "Los Angeles"}', "Robin was born in 1968"),]) def test_compute_birth_year_4(input_data, expected):
assert compute_birth_year(input_data) == expected
在标记中,我们定义了输入数据和预期值,然后定义了一个输入值列表(每个元素都是一个元组)。
Pytest 会将这个输入值列表传递给测试。非常简洁!
5. 在运行时提供 JSON
将 JSON 解析到单元测试中的最后一种方法是在运行时将其作为命令行界面(CLI)输入提供。
为此,我们在 conftest.py 中定义几个 fixture。
conftest.py
def pytest_addoption(parser): parser.addoption( "--input-json", action="store", default='{"name":"Rick", "age": 50, "city": "NY"}', help="Input JSON") @pytest.fixture def input_json_command_line(request): return request.config.getoption("--input-json")
我们使用内置的解析器将 --input-json 标志解析给 Pytest,指定一个默认值,最后将其用作 fixture。
然后我们可以在单元测试中使用它。
tests/unit/test_read_json.py
@pytest.fixture def input_json_command_line(request): return request.config.getoption("--input-json")
这种方法虽然简单,但扩展性不好,特别是当你需要解析多个 JSON 时。
运行单元测试
要运行单元测试,只需运行:
pytest tests/unit/test_read_json.py -v -s --input-json="{'name': 'Ravi', 'age': 90, 'city': 'New Delhi'}"
结论
在本文中,我们研究了在单元测试中解析 JSON 的 5 种方法。
在编写单元测试、集成测试、端到端测试或 API 测试时,知道如何解析 JSON 尤为重要,因为几乎所有的数据交换都是通过 JSON 格式进行的。
你学习了各种技术,包括使用 fixture 和参数、通过外部文件和命令行传递 JSON。
有了这些知识,你现在可以在 Python 单元测试中高效地解析 JSON 数据了。