Почему фиктивный патч работает только при запуске определенного теста, а не всего набора тестов?

Я использую Django и Pytest специально для запуска набора тестов и пытаюсь проверить, отображается ли конкретная форма с ожидаемыми данными, когда пользователь заходит на сайт (интеграционный тест).

В этом конкретном представлении используется хранимая процедура, над которой я издеваюсь, поскольку тест никогда не будет иметь к ней доступа.

Мой тестовый код выглядит так:

#test_integrations.py

from my_app.tests.data_setup import setup_data, setup_sb7_data
from unittest.mock import patch

...

# Setup to use a non-headless browser so we can see whats happening for debugging
@pytest.mark.usefixtures("standard_browser")
class SeniorPageTestCase(StaticLiveServerTestCase):
    """
    These tests surround the senior form
    """

    @classmethod
    def setUpClass(cls):
        cls.host = socket.gethostbyname(socket.gethostname())
        super(SeniorPageTestCase, cls).setUpClass()

    def setUp(self):
        # setup the dummy data - this works fine
        basic_setup(self)
        # setup the 'results'
        self.sb7_mock_data = setup_sb7_data(self)

    @patch("my_app.utils.get_employee_sb7_data")
    def test_senior_form_displays(self, mock_sb7_get):
        # login the dummy user we created
        login_user(self, "futureuser")
        # setup the results
        mock_sb7_get.return_value = self.sb7_mock_data
        # hit the page for the form
        self.browser.get(self.live_server_url + "/my_app/senior")
        form_id = "SeniorForm"
        # assert that the form displays on the page
        self.assertTrue(self.browser.find_element_by_id(form_id))
# utils.py

from django.conf import settings
from django.db import connections


def get_employee_sb7_data(db_name, user_number, window):
    """
    Executes the stored procedure for getting employee data

    Args:
        user_number: Takes the user_number
        db (db connection): Takes a string of the DB to connect to

    Returns:

    """
    cursor = connections[db_name].cursor()
    cursor.execute(
        'exec sp_sb7 %s, "%s"' % (user_number, window.senior_close)
    )
    columns = [col[0] for col in cursor.description]
    results = [dict(zip(columns, row)) for row in cursor.fetchall()]
    return results
# views.py

from myapp.utils import (
    get_employee_sb7_data,
)

...

###### Senior ######
@login_required
@group_required("user_senior")
def senior(request):

    # Additional Logic / Getting Other Models here

    # Execute stored procedure to get data for user
    user_number = request.user.user_no
    results = get_employee_sb7_data("production_db", user_number, window)
    if not results:
        return render(request, "users/senior_not_required.html")

    # Additional view stuff

    return render(
        request,
        "users/senior.html",
        {
            "data": data,
            "form": form,
            "results": results,
        },
    )

Если я запущу этот тест с помощью:

pytest my_app/tests/test_integrations.py::SeniorPageTestCase

Тесты проходят без проблем. Появляется браузер - появляется форма с фиктивными данными, как мы и ожидали, и все работает.

Однако, если я запускаю:

pytest my_app

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

Он пытается вызвать настоящую хранимую процедуру (что не удается, потому что ее еще нет на рабочем сервере) и терпит неудачу.

Почему он исправляет правильно, когда я специально вызываю этот TestCase, но не исправляет правильно, когда я просто запускаю pytest на уровне приложения или проекта?

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

Вы уверены, что это причина? @pytest.mark.usefixtures("standard_browser") — это атрибут класса. Таким образом, он повторно используется всеми тестами. Может ли это вызвать такие же симптомы? То же самое относится и к setUp() без tearDown(), выполняющего всю необходимую очистку.

user1600649 14.12.2020 22:01

Тесты работают при индивидуальном запуске, но не при запуске на уровне приложения или проекта. Это приспособление просто открывает браузер и больше ничего не делает — закрывается по завершении теста — поэтому я не верю, что это имеет какое-либо влияние. Он работает, когда тестируется сам по себе. Кроме того, setUp() настраивает данные, которые Django автоматически усекает между тестами, поэтому нечего очищать. Ошибка в журналах конкретно показывает, что он не запускает это приспособление и вместо этого пытается запустить хранимую процедуру, поэтому исправление не применяется при запуске всего набора.

Hanny 14.12.2020 22:13

Этот метод импортируется из другого места при запуске полного набора? Причина

user1600649 14.12.2020 23:19

Не то, чтобы я знал, если только pytest не делает что-то другое при запуске полного набора по сравнению с определенным классом тестов (что я не верю, что это так); Я добавил служебную функцию и представление, которое вызывает эту функцию, к вопросу для облегчения понимания того, что происходит.

Hanny 15.12.2020 14:54

Что ж, это показывает мне, что есть 2 разных символа: get_employee_sb7_data и my_app.utils.get_employee_sb7_data. Они из одного места, но с разными именами. Таким образом, разница будет заключаться в том, что views.py импортируется раньше test_integrations.py и каким-то образом все еще находится в области видимости, но при отдельном запуске он сначала импортирует тестовый пример (что он и делает). Способ проверить теорию состоит в том, чтобы добавить views.py в качестве импорта в тестовом случае, и тогда оба должны демонстрировать одинаковое (не работающее) поведение.

user1600649 15.12.2020 15:53

Вы правы, если я добавлю import views.py в свой файл test_integrations.py - та же ошибка возникает, когда я запускаю конкретно этот тестовый класс (а также когда я запускаю весь набор). Я издеваюсь/исправляю это совершенно неправильно в этом случае?

Hanny 15.12.2020 16:57
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
6
2 966
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Итак, что происходит, так это то, что ваши представления импортируются до того, как вы вносите исправления.

Давайте сначала посмотрим рабочий случай:

  1. pytest импортирует файл test_integrations
  2. выполняется тест и запускается внутренняя функция декоратора патчей
  3. пока нет импорта утилит, поэтому патч импортирует и заменяет функцию
  4. выполняется тестовое тело, которое передает URL-адрес тестовому клиенту
  5. тестовый клиент импортирует распознаватель и, в свою очередь, импортирует представления, которые импортируют утилиты.
  6. Поскольку утилиты уже пропатчены, все работает нормально.

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

Ваше решение состоит в том, чтобы ссылаться на один и тот же символ. Итак, в test_integrations.py:

@patch("myapp.views.get_employee_sb7_data")

Я добавил импорт вверху файла (test_integrations.py), как вы предложили, затем изменил декоратор функций этого тестового класса на @patch("get_employee_sb7_data"), но получил ошибку: TypeError: Need a valid target to patch. You supplied: 'get_employee_sb7_data'

Hanny 15.12.2020 18:01

Виноват. Обновлено. Символ существует как абсолютный импорт, но как «имя», а не цель.

user1600649 15.12.2020 19:39

Спасибо за отличное объяснение, а также исправление того, что должно произойти. Теперь я лучше понимаю, что происходит (хотя Mocks все еще немного сбивает меня с толку — документация кажется такой же ясной, как грязь! Ха!). Я ценю это!

Hanny 15.12.2020 19:51

Другие вопросы по теме