Pytest, где хранить ожидаемые данные

Функция тестирования Мне нужно передать параметры и увидеть, что результат соответствует ожидаемому результату.

Легко, когда ответ функции - это просто малый массив или однострочная строка, которая может быть определена внутри тестовой функции, но предположим, что функция я test изменяет файл конфигурации, который может быть огромным. Или результирующий массив имеет длину 4 строки, если я определяю его явно. Где я могу хранить это, поэтому мои тесты остаются чистыми и легкими в обслуживании?

Прямо сейчас, если это строка, я просто помещаю файл рядом с тестом .py и выполняю open() внутри теста:

def test_if_it_works():
    with open('expected_asnwer_from_some_function.txt') as res_file:
        expected_data = res_file.read()
    input_data = ... # Maybe loaded from a file as well
    assert expected_data == if_it_works(input_data)

Я вижу много проблем с таким подходом, как проблема поддержания этого файла в актуальном состоянии. Это выглядит плохо. Я могу сделать вещи, вероятно, лучше, переместив это на приспособление:

@pytest.fixture
def expected_data()
    with open('expected_asnwer_from_some_function.txt') as res_file:
        expected_data = res_file.read()
    return expected_data

@pytest.fixture
def input_data()
    return '1,2,3,4'

def test_if_it_works(input_data, expected_data):
    assert expected_data == if_it_works(input_data)

Это просто переводит проблему в другое место, и обычно мне нужно проверить, работает ли функция в случае пустого ввода, ввода одного элемента или нескольких элементов, поэтому я должен создать одно большое приспособление, включающее все три случая или несколько приборов. В конце код становится довольно грязным.

Если функция ожидает сложный словарь в качестве ввода или возвращает словарь того же самого большого тестового кода размера, он становится уродливым:

 @pytest.fixture
 def input_data():
     # It just an example
     return {['one_value': 3, 'one_value': 3, 'one_value': 3,
     'anotherky': 3, 'somedata': 'somestring'], 
      ['login': 3, 'ip_address': 32, 'value': 53, 
      'one_value': 3], ['one_vae': 3, 'password': 13, 'lue': 3]}

Сложно читать тесты с такими приборами и поддерживать их в актуальном состоянии.

Update

После некоторого поиска я нашел библиотеку, которая решила часть проблемы, когда вместо больших файлов конфигурации у меня были большие ответы HTML. Это betamax.

Для упрощения использования я создал приспособление:

from betamax import Betamax

@pytest.fixture
def session(request):
    session = requests.Session()
    recorder = Betamax(session)
    recorder.use_cassette(os.path.join(os.path.dirname(__file__), 'fixtures', request.function.__name__)
    recorder.start()
    request.addfinalizer(recorder.stop)
    return session

Итак, теперь в моих тестах я просто использую приспособление session, и каждый запрос, который я делаю, автоматически сериализуется в файл fixtures/test_name.json, поэтому в следующий раз, когда я выполняю тест вместо того, чтобы делать реальную библиотеку запросов HTTP, он загружает его из файловая система:

def test_if_response_is_ok(session):
   r = session.get("http://google.com")

Это очень удобно, потому что для того, чтобы обновлять эти светильники, мне просто нужно очистить папку fixtures и повторить мои тесты.

Ответ 1

У меня была аналогичная проблема, когда я должен проверить файл конфигурации с ожидаемым файлом. Вот как я его исправил:

  • Создайте папку с тем же именем вашего тестового модуля и в том же месте. Поместите все ожидаемые файлы внутри этой папки.

    test_foo/
        expected_config_1.ini
        expected_config_2.ini
    test_foo.py
    
  • Создайте устройство, ответственное за перемещение содержимого этой папки во временный файл. Я использовал инструмент tmpdir для этого вопроса.

    from __future__ import unicode_literals
    from distutils import dir_util
    from pytest import fixture
    import os
    
    
    @fixture
    def datadir(tmpdir, request):
        '''
        Fixture responsible for searching a folder with the same name of test
        module and, if available, moving all contents to a temporary directory so
        tests can use them freely.
        '''
        filename = request.module.__file__
        test_dir, _ = os.path.splitext(filename)
    
        if os.path.isdir(test_dir):
            dir_util.copy_tree(test_dir, bytes(tmpdir))
    
        return tmpdir
    
  • Используйте свое новое приспособление.

    def test_foo(datadir):
        expected_config_1 = datadir.join('expected_config_1.ini')
        expected_config_2 = datadir.join('expected_config_2.ini')
    

Помните: datadir - это то же самое, что и tmpdir fixture, а также возможность работы с вашими ожидаемыми файлами, помещенными в папку с самим именем тестового модуля.

Ответ 2

Если у вас есть только несколько тестов, то почему бы не включить данные в строковый литерал:

expected_data = """
Your data here...
"""

Если у вас есть горстка, или ожидаемые данные действительно длинны, я думаю, что ваше использование светильников имеет смысл.

Однако, если у вас их много, возможно, другое решение будет лучше. На самом деле, для одного проекта у меня более ста файлов ввода и ожидаемого вывода. Поэтому я построил собственную тестовую среду (более или менее). Я использовал Нос, но PyTest тоже будет работать. Я создал тестовый генератор, который шел по каталогу тестовых файлов. Для каждого входного файла был получен тест, который сравнивал фактический результат с ожидаемым выходом (PyTest называет его параметризацией). Затем я документировал свою структуру, чтобы другие могли ее использовать. Чтобы просмотреть и/или отредактировать тесты, вы редактируете только входные и/или ожидаемые выходные файлы и никогда не должны смотреть на тестовый файл python. Чтобы включить различные входные файлы для определения разных опций, я также создал файл конфигурации YAML для каждой директории (JSON также работал бы, чтобы сохранить зависимости ниже). Данные YAML состоят из словаря, в котором каждый ключ является именем входного файла, а значение является словарем ключевых слов, который будет передан проверяемой функции вместе с входным файлом. Если вам интересно, вот исходный код и документация. Недавно я играл с идеей определения параметров Unittests здесь (требуется только встроенная unittest lib), но я не уверен, что я как это.

Ответ 3

Подумайте, действительно ли нужно проверить содержимое содержимого конфигурации.

Если необходимо проверить только несколько значений или подстрок, подготовьте ожидаемый шаблон для этой конфигурации. Тестируемые места будут отмечены как "переменные" специальным синтаксисом. Затем подготовьте отдельный ожидаемый список значений переменных в шаблоне. Этот ожидаемый список может быть сохранен как отдельный файл или непосредственно в исходном коде.

Пример для шаблона:

ALLOWED_HOSTS = ['{host}']
DEBUG = {debug}
DEFAULT_FROM_EMAIL = '{email}'

Здесь переменные шаблона помещаются внутри фигурных скобок.

Ожидаемые значения могут выглядеть так:

host = www.example.com
debug = False
email = [email protected]

или даже как простой список, разделенный запятыми:

www.example.com, False, [email protected]

Затем ваш тестовый код может вывести ожидаемый файл из шаблона, заменив переменные на ожидаемые значения. И ожидаемый файл сравнивается с фактическим.

Поддержание шаблона и ожидаемых значений по отдельности имеет и преимущество, что вы можете иметь множество наборов данных тестирования с использованием одного и того же шаблона.

Тестирование только переменных

Еще лучший подход заключается в том, что метод создания конфигурации создает только необходимые значения для файла конфигурации. Эти значения могут быть легко вставлены в шаблон другим способом. Но преимущество заключается в том, что тестовый код может напрямую сравнивать все конфигурационные переменные отдельно и четко.

Шаблоны

В то время как легко заменить переменные необходимыми значениями в шаблоне, есть готовые библиотеки шаблонов, которые позволяют делать это только в одной строке. Вот лишь несколько примеров: Django, Jinja, Mako