Как обрабатывать импорт при тестировании приложений Flask: AssertionError: отображение функции отображения перезаписывает существующую функцию конечной точки - PullRequest
0 голосов
/ 03 сентября 2018

Я пытаюсь понять, как писать тесты во Flask.

Я унаследовал приложение, которое уже имеет кучу тестов, которые проверяют маршруты типа /login и проверяют, что ответ соответствует ожидаемому.

У меня значительно более сложная ситуация. Мне нужно протестировать маршрут / метод, который в зависимости от обстоятельств обращается к внешнему API, выясняет, существует ли путь в контейнере, в котором выполняется само приложение, запускает процесс, который занимает более 10 минут для запуска на другом компьютере - - всевозможные вещи. Поэтому я не могу просто отправиться в путь и посмотреть, получил ли я то, что хотел; Мне нужны насмешки и исправления, чтобы имитировать эффекты различных состояний внешнего мира.

Прямо сейчас у меня есть маршрут, определенный так в brain_db/views.py:

@app.route('/label_view/<int:scan_number>')
@login_required
def label_view(scan_number):
    <so much complicated logic>

Первый маршрут, определенный в том же файле, brain_db/views.py, это

@app.route('/surface_test')
def surface_test():
    <some code>

Вот упрощенная версия файла, который выдает ошибку:

import unittest

from mock import MagicMock, patch
from flask_brain_db.test_helpers import set_up, tear_down
from flask_brain_db.brain_db.models import Scan
from brain_db.views import label_view

class BrainDBTest(unittest.TestCase):

    def setUp(self):
        app, db = set_up()

        scan = Scan(1, '000001', '000001_MR1', 'scan.nii.gz', scan_number=1)
        db.session.add(scan)
        scan = Scan.query.filter(Scan.scan_number == 1).first()
        db.session.commit()

    def tearDown(self):
        tear_down()

    def mock_volume_views_setup(self)

        scan = Scan.query.filter(Scan.scan_number == 1).first()
        container_file_path = '/path/to/file/in/container'
        return scan, container_file_path

    def mock_os_path_exists(self, arg):
        return True

    @patch('brain_db_helpers.volume_views_setup', mock_volume_views_setup)
    @patch('os.path.exists', mock_os_path_exists)
    def test_label_view(self):
        rv = label_view(1)
        assert(True) # I'll actually write tests when I figure out that I can!
        print rv

Вот ошибка:

======================================================================
ERROR: brain_db.tests.test (unittest.loader.ModuleImportFailure)
----------------------------------------------------------------------
ImportError: Failed to import test module: brain_db.tests.test
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/unittest/loader.py", line 254, in _find_tests
    module = self._get_module_from_name(name)
  File "/usr/local/lib/python2.7/unittest/loader.py", line 232, in _get_module_from_name
    __import__(name)
  File "/usr/src/app/flask_brain_db/brain_db/tests/test.py", line 7, in <module>
    from brain_db.views import label_view
  File "/usr/src/app/flask_brain_db/brain_db/views.py", line 36, in <module>
    @app.route('/surface_test')
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1250, in decorator
    self.add_url_rule(rule, endpoint, f, **options)
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 66, in wrapper_func
    return f(self, *args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1221, in add_url_rule
    'existing endpoint function: %s' % endpoint)
AssertionError: View function mapping is overwriting an existing endpoint function: surface_test

Что я сделал, чтобы попытаться решить мою проблему: я прочитал кучу постов на SO, которые цитируют ту же ошибку AssertionError. Например. 1 , 2 . Я вижу, что общая проблема заключается в том, что мои маршруты уже определены, и

from brain_db.views import label_view

снова выполняет модуль views, переопределяя маршруты, поэтому я получаю сообщение об ошибке.

Чего я не понимаю, так это как именно мне следует этого избегать. Мне нужно иметь возможность импортировать метод в другой файл, чтобы иметь возможность проверить его. Все ли маршруты обернуты в if __name__ == main? Я новичок в разработке Flask и еще не видел пример кода, где это так; Я сомневаюсь, что это правильное решение; это единственное, что предлагается, когда вы пытаетесь найти, чтобы предотвратить выполнение кода при импорте.

Сейчас я запускаю свои тесты через файл manage.py в верхнем уровне моего приложения. Он содержит следующий метод:

@manager.command
def test():
    """Runs the tests without coverage"""
    tests = unittest.TestLoader().discover(start_dir='.', pattern='test*.py')
    res = unittest.TextTestRunner(verbosity=2).run(tests)
    sys.exit(not res.wasSuccessful())

Я запускаю python manage.py test в командной строке.

Также может быть уместно, что, хотя я поместил тест, который не проходит, в подмодуле в пределах brain_db, перед ним запускается несколько тестов, которые проверяют маршруты, определенные в приложении, и тестируют ожидаемый результат. Однако комментирование этих тестов не влияет на то, как мой тест не проходит.

Наконец-то я сначала получил ошибку в строке from flask_brain_db.brain_db.models import Scan:

ERROR: brain_db.tests.test (unittest.loader.ModuleImportFailure)
----------------------------------------------------------------------
ImportError: Failed to import test module: brain_db.tests.test
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/unittest/loader.py", line 254, in _find_tests
    module = self._get_module_from_name(name)
  File "/usr/local/lib/python2.7/unittest/loader.py", line 232, in _get_module_from_name
    __import__(name)
  File "/usr/src/app/flask_brain_db/brain_db/tests/test.py", line 5, in <module>
    from flask_brain_db.brain_db.models import Scan
  File "/usr/src/app/flask_brain_db/brain_db/models.py", line 6, in <module>
    class Scan(db.Model):
  File "/usr/local/lib/python2.7/site-packages/flask_sqlalchemy/model.py", line 67, in __init__
    super(NameMetaMixin, cls).__init__(name, bases, d)
  File "/usr/local/lib/python2.7/site-packages/flask_sqlalchemy/model.py", line 121, in __init__
    super(BindMetaMixin, cls).__init__(name, bases, d)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py", line 65, in __init__
    _as_declarative(cls, classname, cls.__dict__)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 116, in _as_declarative
    _MapperConfig.setup_mapping(cls, classname, dict_)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 144, in setup_mapping
    cfg_cls(cls_, classname, dict_)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 172, in __init__
    self._setup_table()
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 465, in _setup_table
    **table_kw)
  File "/usr/local/lib/python2.7/site-packages/flask_sqlalchemy/model.py", line 90, in __table_cls__
    return sa.Table(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/sql/schema.py", line 439, in __new__
    "existing Table object." % key)
InvalidRequestError: Table 'scan' is already defined for this MetaData instance.  Specify 'extend_existing=True' to redefine options and columns on an existing Table object.

Я убрал его, включив

__table_args__ = {'extend_existing': True}

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

Как правильно подойти к этому? Пожалуйста, дайте мне знать, если мне нужно предоставить какую-либо другую информацию.

1 Ответ

0 голосов
/ 04 сентября 2018

Вы должны иметь доступ ко всем функциям просмотра из объекта приложения. Попробуйте удалить строку «из brain_db.views import label_view» и вместо этого определите свой метод label_view после запуска set_up (), используя следующую команду:

label_view = app.view_functions ["label_view"]

...