Как протестировать бесконечный цикл while с помощью pytest

В настоящее время я пишу небольшую библиотеку, которая взаимодействует с сервером сборки bamboo. Тестирование проводится с помощью pytest. Я застрял на следующей проблеме. Я хочу протестировать цикл while, который выполняется до тех пор, пока какой-либо статус не будет удовлетворен. Прочитав документ pytest, я попытался «издеваться»/обезьянить статус, но на самом деле это не работает. Я, вероятно, делаю что-то элементарное неправильно здесь: это цикл while, о котором идет речь:

    # determine current status
    running = self._is_a_build_running()

    # turn on and off running powerplug while building
    while running:
        self.feedback.turn_off_success()
        self.feedback.turn_on_running()
        time.sleep(self.blinker_time)
        self.feedback.turn_off_running()
        self._update_builds_status()
        running = self._is_a_build_running()

поэтому с помощью pytest я пытался создать фикстуру для положительного и отрицательного _is_a_build_running следующим образом:

@pytest.fixture(scope='function')
def mock_is_a_build_running():
    return False

а затем с помощью этого тестового метода с использованием ThreadPool (объяснено здесь как получить возвращаемое значение из потока в python?), потому что мне также понадобится результат от метода, содержащего цикл while.

def test_update_status_running(bamboopickups, monkeypatch,
                   mock_update_overall_data_positive,
                   mock_update_builds_status_positive,
                   mock_is_a_build_running):
monkeypatch.setattr('BambooPickup._update_overall_data', lambda x: mock_update_overall_data_positive)
monkeypatch.setattr('BambooPickup._update_builds_status', lambda x: mock_update_builds_status_positive)

pool = ThreadPool(processes=1)
async_result = pool.apply_async(bamboopickups.update_status())

monkeypatch.setattr('BambooPickup._update_overall_data', lambda x: mock_update_overall_data_positive)
monkeypatch.setattr('BambooPickup._is_a_build_running', lambda x: mock_is_a_build_running)

actual = async_result.get()
expected = True
assert actual == expected

Вероятно, это легко сделать с помощью pytest-mock, но пока я использовал только предпочтительный способ, описанный здесь: http://pytest.org/latest/monkeypatch.html.


person maiksensi    schedule 24.12.2014    source источник


Ответы (3)


Итак, еще немного покопавшись в этом вопросе, я нашел решение, которое меня пока удовлетворяет. Я хочу поделиться им, если кто-то еще столкнется с той же проблемой. На самом деле это довольно просто и с некоторым вспомогательным классом из https://gist.github.com/daltonmatos/3280885 Я придумал следующий тестовый код:

def test_update_status_running(bamboopickup, monkeypatch,
                               mock_update_overall_data_positive,
                               mock_update_builds_status_positive):
    monkeypatch.setattr('pickups.bamboo.bamboopickup.BambooPickup._update_overall_data', lambda x: mock_update_overall_data_positive)
    monkeypatch.setattr('pickups.bamboo.bamboopickup.BambooPickup._update_builds_status', lambda x: mock_update_builds_status_positive)

    with mock.patch.object(bamboopickup, '_is_a_build_running') as mockfoo:
        mockfoo.return_value = AlmostAlwaysTrue(2)
        bamboopickup.update_status()

и вспомогательный класс:

class AlmostAlwaysTrue(object):
    def __init__(self, total_iterations=1):
        self.total_iterations = total_iterations
        self.current_iteration = 0

    def __nonzero__(self):
        if self.current_iteration < self.total_iterations:
            self.current_iteration += 1
            return bool(1)
        return bool(0)

    # Python >= 3
    def __bool__(self):
        if self.current_iteration < self.total_iterations:
            self.current_iteration += 1
            return bool(1)
        return bool(0)

Можно также изменить его, чтобы в какой-то момент вернуть исключение и проверить его снова. Я держу вопрос немного дольше открытым, на случай, если у кого-то есть более чистое решение (в чем я уверен).

person maiksensi    schedule 24.12.2014

Я добавляю параметр running_times_for_test (по умолчанию -1) для такого рода функций, если running_times достигает 0, то я прерываю бесконечный цикл. Если running_time равно -1 (значение по умолчанию), делайте как обычно.

person Simin Jie    schedule 13.02.2017
comment
звучит как еще один правильный подход к этому вопросу :) - person maiksensi; 11.07.2017

Как уже упоминалось @Simin Jie, вы можете использовать контраргумент по умолчанию. Вот пример:

Сам код

def start(number_of_iterations=-1):
    while number_of_iterations != 0:
        # HERE IS THE MAIN LOGIC
        number_of_iterations -= 1
    return 'BLA BLA BLA'

Тест

def test_start(self):
    results = start(
        number_of_iterations=1,
    )
    self.assertEqual(
        first=results,
        second='BLA BLA BLA',
    )
person Alon Barad    schedule 17.12.2020