JavaScript 对象表示法(JSON)可以说是网络上最流行的数据交换格式之一。



Web 服务使用序列化技术将数据从底层数据结构转换为 JSON 格式,以便接收服务能够轻松地对其进行反序列化。



在编写单元测试时,测试 JSON 输入和输出的需求至关重要。



测试数据、API 响应,有时甚至配置文件都是用 JSON 定义的,这使得使用 Pytest 理解如何读取和写入 JSON 变得十分必要。



在本文中,我们将探索在 Pytest 中读取 JSON 数据的 5 种简单方法,这样你就可以用它来验证输入的测试数据、配置文件、API 响应等等。



目标



在本文结束时,你应该能够:



  1. 使用 5 种不同的方法在 Pytest 中解析 JSON 数据。
  2. 理解如何使用 Pytest 的 fixture(夹具)和参数将 JSON 数据解析到单元测试中。


入门



在开始操作前,请按以下目录组织你的代码结构

Pytest中5种不同的方法解析JSON数据_log4j



本文中不需要特殊的库,只需要 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种不同的方式来测试这个函数。



  1. 在测试中定义 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'}"

Pytest中5种不同的方法解析JSON数据_pytest_02



结论



在本文中,我们研究了在单元测试中解析 JSON 的 5 种方法。



在编写单元测试、集成测试、端到端测试或 API 测试时,知道如何解析 JSON 尤为重要,因为几乎所有的数据交换都是通过 JSON 格式进行的。



你学习了各种技术,包括使用 fixture 和参数、通过外部文件和命令行传递 JSON。



有了这些知识,你现在可以在 Python 单元测试中高效地解析 JSON 数据了。