Pytest с линией JSON - PullRequest
       17

Pytest с линией JSON

2 голосов
/ 15 апреля 2019

Я относительно новичок в 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, чтобы сделать это, но это будет проверять каждый ключ.Можно ли делать то, о чем я думаю, или лучше создавать отдельные приборы, когда у меня разные значения для утверждений?

1 Ответ

1 голос
/ 17 апреля 2019

Я думаю, что наиболее подходящим для вас подходом будет параметризация прибора:

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

Редактировать 2: использование косвенной параметризации

Другим и, возможно, более чистым подходом было бы позволить устройству tweet обрабатывать только твит из необработанной строки, передавая параметризацию в сам тест. Я использую косвенную параметризацию , чтобы передать необработанную строку в устройство 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
...