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

#django #unit-testing #mocking #pytest #liveservertestcase

#django #модульное тестирование #издевательство #pytest #liveservertestcase

Вопрос:

Я использую 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

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

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

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

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

Комментарии:

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

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

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

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

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

Ответ №1:

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

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

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

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

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

 @patch("myapp.views.get_employee_sb7_data")
 

Комментарии:

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

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

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