Pytest 用线划定 json
Pytest with line-delineated json
我是 Python 的新手,也是 pytest
的新手。无论如何,我正在尝试编写一些测试来解析以行为单位的推文 json。这是一个简化的例子 test_cases.jsonl
:
{"contributors":null,"coordinates":null,"created_at":"Sat Aug 20 01:00:12 +0000 2016","entities":{"hashtags":[{"indices":[97,116],"text":"StandWithLouisiana"}]}}
{"contributors":null,"coordinates":null,"created_at":"Sat Aug 20 01:01:35 +0000 2016","entities":{"hashtags":[]}}
我想做的是测试如下函数:
def hashtags(t):
return ' '.join([h['text'] for h in t['entities']['hashtags']])
我可以按如下方式测试 JSON 的单行:
@pytest.fixture
def tweet(file='test_cases.jsonl'):
with open(file, encoding='utf-8') as lines:
for line in lines:
return json.loads(line)
def test_hashtag(tweet):
assert hashtags(tweet) == 'StandWithLouisiana'
(我只是将文件名作为此示例函数的参数)
这在测试通过的意义上是有效的,因为第一行通过了测试,但我基本上想做的是类似这样的事情,我不希望它像它写的那样工作。
def test_hashtag(tweet):
assert hashtags(tweet) == 'StandWithLouisiana' # first tweet
assert hashtags(tweet) == '' # second tweet
这失败了,因为它测试第一条推文(json 中的行)是否为空,而不是第二条推文。我假设这是因为夹具中的 return
,但如果我尝试 yield
而不是 return
,我会得到一个 yield_fixture function has more than one 'yield'
错误(并且第二行仍然失败).
为了解决这个问题,我现在正在做的是让每一行成为一个单独的 JSON 文件,然后为每个文件创建一个单独的固定装置。
(对于较短的示例,我使用 StringIO
来编写 JSON 内联)。
这确实有效,但感觉不雅。我觉得我应该为此使用 @pytest.mark.parametrize
,但我无法理解它。我想我也尝试 pytest_generate_tests
这样做,但它会测试每个键。是否可以按照我的想法进行操作,或者当我对断言有不同的值时创建单独的装置是否更好?
我认为最适合您的方法是对夹具进行参数化:
import json
import pathlib
import pytest
lines = pathlib.Path('data.json').read_text().split('\n')
@pytest.fixture(params=lines)
def tweet(request):
line = request.param
return json.loads(line)
def hashtags(t):
return ' '.join([h['text'] for h in t['entities']['hashtags']])
def test_hashtag(tweet):
assert hashtags(tweet) == 'StandWithLouisiana'
这将对 tweet
的每个返回值调用一次 test_hashtag
:
$ pytest -v
...
test_spam.py::test_hashtag[{"contributors":null,"coordinates":null,"created_at":"Sat Aug 20 01:00:12 +0000 2016","entities":{"hashtags":[{"indices":[97,116],"text":"StandWithLouisiana"}]}}]
test_spam.py::test_hashtag[{"contributors":null,"coordinates":null,"created_at":"Sat Aug 20 01:01:35 +0000 2016","entities":{"hashtags":[]}}]
...
编辑:扩展夹具以提供预期值
您可以将预期值包含到 tweet
夹具参数中,然后将其原封不动地传递给测试。在下面的示例中,预期的标签与文件行一起压缩以构建 (line, tag)
形式的对。 tweet
fixture 将行加载到字典中,通过标记,因此测试中的 tweet
参数变成一对值。
import json
import pathlib
import pytest
lines = pathlib.Path('data.json').read_text().split('\n')
expected_tags = ['StandWithLouisiana', '']
@pytest.fixture(params=zip(lines, expected_tags),
ids=tuple(repr(tag) for tag in expected_tags))
def tweet(request):
line, tag = request.param
return (json.loads(line), tag)
def hashtags(t):
return ' '.join([h['text'] for h in t['entities']['hashtags']])
def test_hashtag(tweet):
data, tag = tweet
assert hashtags(data) == tag
测试 运行 像以前一样产生两个测试:
test_spam.py::test_hashtag['StandWithLouisiana'] PASSED
test_spam.py::test_hashtag[''] PASSED
编辑 2:使用间接参数化
另一种可能更简洁的方法是让 tweet
fixture 仅处理从原始字符串解析推文,将参数化移动到测试本身。我在这里使用 indirect parametrization 将原始行传递给 tweet
固定装置:
import json
import pathlib
import pytest
lines = pathlib.Path('data.json').read_text().split('\n')
expected_tags = ['StandWithLouisiana', '']
@pytest.fixture
def tweet(request):
line = request.param
return json.loads(line)
def hashtags(t):
return ' '.join([h['text'] for h in t['entities']['hashtags']])
@pytest.mark.parametrize('tweet, tag',
zip(lines, expected_tags),
ids=tuple(repr(tag) for tag in expected_tags),
indirect=('tweet',))
def test_hashtag(tweet, tag):
assert hashtags(tweet) == tag
测试 运行 现在还会产生两个测试:
test_spam.py::test_hashtag['StandWithLouisiana'] PASSED
test_spam.py::test_hashtag[''] PASSED
我是 Python 的新手,也是 pytest
的新手。无论如何,我正在尝试编写一些测试来解析以行为单位的推文 json。这是一个简化的例子 test_cases.jsonl
:
{"contributors":null,"coordinates":null,"created_at":"Sat Aug 20 01:00:12 +0000 2016","entities":{"hashtags":[{"indices":[97,116],"text":"StandWithLouisiana"}]}}
{"contributors":null,"coordinates":null,"created_at":"Sat Aug 20 01:01:35 +0000 2016","entities":{"hashtags":[]}}
我想做的是测试如下函数:
def hashtags(t):
return ' '.join([h['text'] for h in t['entities']['hashtags']])
我可以按如下方式测试 JSON 的单行:
@pytest.fixture
def tweet(file='test_cases.jsonl'):
with open(file, encoding='utf-8') as lines:
for line in lines:
return json.loads(line)
def test_hashtag(tweet):
assert hashtags(tweet) == 'StandWithLouisiana'
(我只是将文件名作为此示例函数的参数)
这在测试通过的意义上是有效的,因为第一行通过了测试,但我基本上想做的是类似这样的事情,我不希望它像它写的那样工作。
def test_hashtag(tweet):
assert hashtags(tweet) == 'StandWithLouisiana' # first tweet
assert hashtags(tweet) == '' # second tweet
这失败了,因为它测试第一条推文(json 中的行)是否为空,而不是第二条推文。我假设这是因为夹具中的 return
,但如果我尝试 yield
而不是 return
,我会得到一个 yield_fixture function has more than one 'yield'
错误(并且第二行仍然失败).
为了解决这个问题,我现在正在做的是让每一行成为一个单独的 JSON 文件,然后为每个文件创建一个单独的固定装置。
(对于较短的示例,我使用 StringIO
来编写 JSON 内联)。
这确实有效,但感觉不雅。我觉得我应该为此使用 @pytest.mark.parametrize
,但我无法理解它。我想我也尝试 pytest_generate_tests
这样做,但它会测试每个键。是否可以按照我的想法进行操作,或者当我对断言有不同的值时创建单独的装置是否更好?
我认为最适合您的方法是对夹具进行参数化:
import json
import pathlib
import pytest
lines = pathlib.Path('data.json').read_text().split('\n')
@pytest.fixture(params=lines)
def tweet(request):
line = request.param
return json.loads(line)
def hashtags(t):
return ' '.join([h['text'] for h in t['entities']['hashtags']])
def test_hashtag(tweet):
assert hashtags(tweet) == 'StandWithLouisiana'
这将对 tweet
的每个返回值调用一次 test_hashtag
:
$ pytest -v
...
test_spam.py::test_hashtag[{"contributors":null,"coordinates":null,"created_at":"Sat Aug 20 01:00:12 +0000 2016","entities":{"hashtags":[{"indices":[97,116],"text":"StandWithLouisiana"}]}}]
test_spam.py::test_hashtag[{"contributors":null,"coordinates":null,"created_at":"Sat Aug 20 01:01:35 +0000 2016","entities":{"hashtags":[]}}]
...
编辑:扩展夹具以提供预期值
您可以将预期值包含到 tweet
夹具参数中,然后将其原封不动地传递给测试。在下面的示例中,预期的标签与文件行一起压缩以构建 (line, tag)
形式的对。 tweet
fixture 将行加载到字典中,通过标记,因此测试中的 tweet
参数变成一对值。
import json
import pathlib
import pytest
lines = pathlib.Path('data.json').read_text().split('\n')
expected_tags = ['StandWithLouisiana', '']
@pytest.fixture(params=zip(lines, expected_tags),
ids=tuple(repr(tag) for tag in expected_tags))
def tweet(request):
line, tag = request.param
return (json.loads(line), tag)
def hashtags(t):
return ' '.join([h['text'] for h in t['entities']['hashtags']])
def test_hashtag(tweet):
data, tag = tweet
assert hashtags(data) == tag
测试 运行 像以前一样产生两个测试:
test_spam.py::test_hashtag['StandWithLouisiana'] PASSED
test_spam.py::test_hashtag[''] PASSED
编辑 2:使用间接参数化
另一种可能更简洁的方法是让 tweet
fixture 仅处理从原始字符串解析推文,将参数化移动到测试本身。我在这里使用 indirect parametrization 将原始行传递给 tweet
固定装置:
import json
import pathlib
import pytest
lines = pathlib.Path('data.json').read_text().split('\n')
expected_tags = ['StandWithLouisiana', '']
@pytest.fixture
def tweet(request):
line = request.param
return json.loads(line)
def hashtags(t):
return ' '.join([h['text'] for h in t['entities']['hashtags']])
@pytest.mark.parametrize('tweet, tag',
zip(lines, expected_tags),
ids=tuple(repr(tag) for tag in expected_tags),
indirect=('tweet',))
def test_hashtag(tweet, tag):
assert hashtags(tweet) == tag
测试 运行 现在还会产生两个测试:
test_spam.py::test_hashtag['StandWithLouisiana'] PASSED
test_spam.py::test_hashtag[''] PASSED