我对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
这失败了,因为它测试了第一个tweet(json中的行)是否为空,而不是第二个。我认为这是因为固定装置中的return
,但是如果我尝试使用yield
而不是return
,则会收到yield_fixture function has more than one 'yield'
错误`(第二行仍然失败)。
我现在要解决此问题的方法是,使每行成为一个单独的JSON文件,然后为它们中的每一个创建一个单独的夹具。
(对于较短的示例,我使用StringIO
来编写JSON内联)。
这确实有效,但感觉不佳。我觉得我应该为此使用@pytest.mark.parametrize
,但是我无法理解。我想我也尝试过pytest_generate_tests
来做到这一点,但是它将测试每个键。有可能做我想做的事情,还是当我对断言有不同的值时创建单独的夹具更好?
答案 0 :(得分:1)
我认为最适合您的方法是对灯具进行参数化设置:
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'
这将用每个返回的test_hashtag
值调用一次tweet
:
$ 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
夹具将行加载到字典中,使标记通过,因此测试中的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
另一种可能更干净的方法是让tweet
固定装置仅处理来自原始字符串的推文解析,将参数化移至测试本身。我正在使用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