如何使用pytest测试无限循环

时间:2014-12-24 13:26:39

标签: python unit-testing mocking pytest monkeypatching

我目前正在编写一个与Bamboo构建服务器交互的小库。使用pytest进行测试。我遇到了以下问题。我想测试一个运行的while循环,直到满足一些状态。阅读pytest doc,我试图“模拟”/ monkeypatch状态,但它并没有真正起作用。我可能在这里做了一些基本的错误: 这是有问题的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(这里解释为how to get the return value from a thread in 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

3 个答案:

答案 0 :(得分:4)

所以,经过一番深入研究,我找到了一个满足我现在的解决方案。我想分享它,以防其他人遇到同样的问题。实际上它非常简单,并且使用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)

也可以修改它以在某个时刻返回异常并再次检查。如果有人有更清洁的解决方案(我相信),我会将问题保持更长时间。

答案 1 :(得分:1)

我为这种函数添加了一个参数running_times_for_test(默认为-1),如果running_times达到0,那么我打破了无限循环。如果running_time等于-1(默认值),则照常执行。

答案 2 :(得分:0)

@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',
    )