Модульное тестирование формы Flask, содержащей несколько кнопок отправки

Я пишу модульные тесты для метода проверки формы в приложении Flask, которое содержит несколько разных кнопок «Отправить» для управления логическим потоком.

Метод проверки формы ожидает получить объект ImmutibleMultiDict, который включает имя кнопки и значение, например ('btn', 'Save') или ('btn', 'Update')или ('btn', 'Delete'). К сожалению, я не могу понять, как издеваться или предоставлять различные ответы кнопок в pytest.

Ниже приведен пример кода из метода проверки формы с некоторыми различными действиями в зависимости от того, какая кнопка использовалась при отправке («Обновить» или «Сохранить»):

def validate(self):
    if request.form['btn'] == 'Update':
            if cn_continent_name and en_continent_name:
                flash('You have not made a change. There is nothing to update.', 'warning')
                return False
            if not _check_clean_chinese():
                return False

    if request.form['btn'] == 'Save':
            # check if Chinese name already exists in the DB
            if cn_continent_name:
                self.cn_name.errors.append("Chinese Continent Name already registered")
                return False
            # check the if English name already exists in the DB
            en_continent_name = ContinentsTable.query.filter_by(en_name=self.en_name.data).first()
            if en_continent_name:
                self.en_name.errors.append("English Country Name already registered")
                return False

Приведенный ниже тест метода проверки формы не работает, поскольку отсутствует информация о значении имени кнопки, которая соответствует тестируемой логике проверки формы, которая ожидает проверки наличия request.form['btn'] = 'Save' или request.form['btn'] = 'Update'.

class TestContinentsForm:
"""Continents form."""

def test_validate_continent_cn_name_already_registered(self, continent):
    """Enter Continent cn_name that is already registered."""
    form = ContinentsForm(cn_name=continent.cn_name, en_name='NewEngName')
    assert form.validate() is False
    assert 'Chinese Continent Name already registered' in form.cn_name.errors

Ниже приведен сбой теста с кодом ошибки, и причина, по которой он имеет ошибку, заключается в том, что проверка ожидает объект werkzeug ImmutibleMutltiDict, который включает имя кнопки, которая использовалась для отправки формы, но я не указал должным образом имя кнопки в объект ImmutibleMultiDict.

Я пробовал десятки вещей, но в приведенном ниже тесте прокомментирован один пример request.form.add('btn','Save'), который не работает, потому что не может напрямую изменять объект ImmutibleMutliDict:

self = <tests.test_forms.TestContinentsForm object at 0x10f8be908>
continent = Asia, 亚洲, yà zhōu!

def test_validate_continent_cn_name_already_registered(self, continent):
    """Enter Continent cn_name that is already registered."""
    form = ContinentsForm(cn_name=continent.cn_name, en_name='NewEngName')
    #request.form.add('btn','Save')
    #assert 'Save' in request.form
    >assert form.validate() is False

test_forms.py:96: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../aoscrdb_app/user/forms/locations/continents_form.py:70: in validate
if 'Delete' in request.form['btn']:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = ImmutableMultiDict([]), key = 'btn'

def __getitem__(self, key):
    """Return the first data value for this key;
        raises KeyError if not found.

        :param key: The key to be looked up.
        :raise KeyError: if the key does not exist.
        """
    if key in self:
        return dict.__getitem__(self, key)[0]
    >raise exceptions.BadRequestKeyError(key)
   E werkzeug.exceptions.HTTPException.wrap.<locals>.newcls: 400: Bad Request

../venv/lib/python3.5/site-packages/werkzeug/datastructures.py:402: BadRequestKeyError

Чтобы правильно протестировать проверку формы, объект ImmutableMultiDict должен выглядеть так, включая данные ('btn', 'Save'):

This is reqest.form =>ImmutableMultiDict([('cn_name', '中地'), ('btn', 'Save'), 
('en_name', 'Middle Earth'), 
('csrf_token', '1455956207##90932fcb2d1481be007f90e32040b6aba3e5fe68')])

Я использую pytest и factory-boy, а ниже приведены соответствующие приспособления pytest и factory. Я пытался создать другие приспособления pytest, которые включают данные кнопки, но это также не сработало для меня:

@pytest.fixture()
def continent(db):
    """A continent for the tests."""
    continent = ContinentFactory()
    db.session.commit()
    return continent

class ContinentFactory(BaseFactory):
"""Continent factory."""
cn_name = '亚洲'
en_name = 'Asia'

class Meta:
    """Factory configuration."""
    model = ContinentsTable

Я считаю, что кнопки должны быть сохранены в словаре, таком как {'btn': 'Save'}, и доступны для тестовой среды, но я не могу найти лучший способ реализации. Спасибо!


person Bob Jordan    schedule 17.02.2016    source источник
comment
В награде выше я имел в виду request.form['btn'] = 'Save' или request.form['btn'] = 'Update'   -  person Bob Jordan    schedule 20.02.2016
comment
Это reqest.form =›ImmutableMultiDict([('cn_name', '中地'), ('btn', 'Сохранить'), ('en_name', 'Средиземье'), ('csrf_token', '1455956207# #90932fcb2d1481be007f90e32040b6aba3e5fe68')])   -  person Bob Jordan    schedule 20.02.2016
comment
Если вы еще этого не видели: flask.pocoo.org/docs/0.10/testing   -  person wgwz    schedule 21.02.2016
comment
Я не уверен, что полностью понимаю, что вы пытаетесь сделать. Вам нужно знать, какая кнопка была нажата внутри вашей функции test_validate_continent_cn_name_already_registered? Или вы пытаетесь присвоить значение кнопке, чтобы имитировать нажатие кнопки «Сохранить» или «Обновить»? Было бы полезно, если бы вы описали (в коде), что вы пытаетесь сделать.   -  person junnytony    schedule 22.02.2016
comment
@junnytony спасибо за отзыв, я обновил вопрос, чтобы уточнить. Мне нужно имитировать нажатие кнопки «Сохранить», «Обновить» или «Удалить». Проверка формы ожидает объект ImmutableMultiDict, который включает, например, ('btn', 'Save) в случае «Сохранить   -  person Bob Jordan    schedule 24.02.2016


Ответы (1)


Если вы пытаетесь проверить свою логику flask (включая поведение формы), у Flask уже есть встроенный способ сделать это, и вы можете ввести свои собственные значения POST, GET: http://flask.pocoo.org/docs/0.10/testing/

Но кажется, что вы пытаетесь проверить именно логику проверки вашей формы. В этом случае вам нужно изменить контекст запроса и ввести значения вашей кнопки в request.form (по сути, замените ImmutableMultiDict() на свой собственный). Это должно быть сделано в контексте запроса. См. ссылку выше.

Ниже приведен пример кода, который показывает, как этого добиться:

Форма

import wtforms
class SampleForm(wtforms.Form):
    btn = wtforms.fields.SubmitField('Cancel')

    def validate(self):
        if request.form['btn'] == 'Save':
            print('Saving...')
        elif request.form['btn'] == 'Update':
            print('Updating!')
        else:
            print('Some other btn action')

Тест

from flask import Flask, request
from werkzeug import ImmutableMultiDict

def test_sample_form_validate():
    app = Flask(__name__)
    form = SampleForm()
    with app.test_request_context('/'):
        request.form = ImmutableMultiDict([('btn', 'Save')])
        form.validate() # Prints 'Saving...'
        request.form = ImmutableMultiDict([('btn', 'Update')])
        form.validate() # Prints 'Updating!'

Запуск функции test_sample_form_validate должен вывести «Сохранение...», а затем «Обновление!». Вам, конечно, нужно будет добавить остальные ваши соответствующие данные в ImmutableMultiDict.

person junnytony    schedule 24.02.2016
comment
Добавление request.form = ImmutableMultiDict([('btn', 'Save')]) решило мои проблемы. Большое спасибо. - person Bob Jordan; 25.02.2016