0

在我正在使用的源代码(此处的源链接和此处WIP PR)中,我试图通过测试类方法try-except中的块来提高测试覆盖率。__init__

从源代码中剥离额外的代码,相关代码如下所示:

# webrtc.py

import asyncio
from loguru import logger
try:
    from asyncio import get_running_loop  # noqa Python >=3.7
except ImportError:  # pragma: no cover
    from asyncio.events import _get_running_loop as get_running_loop  # pragma: no cover

class WebRTCConnection:
    loop: Any

    def __init__(self) -> None:
        try:
            self.loop = get_running_loop()
        except RuntimeError as e:
            self.loop = None
            logger.error(e)
        
        if self.loop is None:
            self.loop = asyncio.new_event_loop()

在一个单独的测试文件中,我想模拟RuntimeError测试try except块:

# webrtc_test.py

from unittest.mock import patch
from unittest.mock import Mock

import asyncio
import pytest
from webrtc import WebRTCConnection

@pytest.mark.asyncio
async def test_init_patch_runtime_error() -> None:
    nest_asyncio.apply()

    with patch("webrtc.get_running_loop", return_value=RuntimeError):
        with pytest.raises(RuntimeError):
            WebRTCConnection()

@pytest.mark.asyncio
async def test_init_mock_runtime_error() -> None:
    nest_asyncio.apply()

    mock_running_loop = Mock()
    mock_running_loop.side_effect = RuntimeError
    with patch("webrtc.get_running_loop", mock_running_loop):
        with pytest.raises(RuntimeError):
            domain = Domain(name="test")
            WebRTCConnection()

两个测试都不会通过,因为两者都不会 raise RuntimeError

asyncio.new_event_loop此外,我试图模拟monkeypatch

# webrtc_test.py

from unittest.mock import patch
from unittest.mock import Mock

import asyncio
import pytest

from webrtc import WebRTCConnection

@pytest.mark.asyncio
async def test_init_new_event_loop(monkeypatch) -> None:
    nest_asyncio.apply()

    WebRTCConnection.loop = None
    mock_new_loop = Mock()
    monkeypatch.setattr(asyncio, "new_event_loop", mock_new_loop)
    WebRTCConnection()

    assert mock_new_loop.call_count == 1

该测试也失败了,因为从未调用过猴子补丁:> assert mock_new_loop.call_count == 1 E assert 0 == 1.

我想知道我在这里做错了什么,我怎么能成功地测试__init__这个类的方法?

非常感谢您的时间!

4

1 回答 1

1

你有两个问题:

  1. 您正在设置 的返回值get_running_loop,但异常不是返回值。如果您希望模拟代码引发异常,则需要配置side_effect

  2. 您的代码捕获RuntimeError并且不会重新引发是:您只需设置self.loop = None并记录一个错误。这意味着即使您成功引发RuntimeErrorfrom get_event_loop,该异常对您的测试也永远不可见,因为它已被您的代码使用。

如果您要模拟您的logger对象,您可以检查logger.error是否调用了异常。例如:

@pytest.mark.asyncio
async def test_init_patch_runtime_error() -> None:
    nest_asyncio.apply()

    with patch("webrtc.logger") as mock_logger:
        with patch("webrtc.get_running_loop", side_effect=RuntimeError()):
            WebRTCConnection()
            assert isinstance(mock_logger.error.call_args[0][0], RuntimeError)

编辑:W/r/t 检查self.loop = None零件,我可能会像这样重写代码:

class WebRTCConnection:
    loop: Any = None

    def __init__(self) -> None:
    ┆   try:
    ┆   ┆   self.loop = get_running_loop()
    ┆   except RuntimeError as e:
    ┆   ┆   logger.error(e)

    ┆   if self.loop is None:
    ┆   ┆   self.loop = asyncio.new_event_loop()

然后在测试时,您需要模拟new_event_loop. 我可能会摆脱嵌套with语句,而只patch在函数上使用装饰器:

@pytest.mark.asyncio
@patch('webrtc.logger')
@patch('webrtc.get_running_loop', side_effect=RuntimeError())
@patch('webrtc.asyncio.new_event_loop', return_value='fake_loop')
async def test_init_patch_runtime_error(
    mock_new_event_loop,
    mock_get_running_loop,
    mock_logger
) -> None:
    nest_asyncio.apply()

    rtc = WebRTCConnection()
    assert isinstance(mock_logger.error.call_args[0][0], RuntimeError)
    assert rtc.loop == 'fake_loop'

...但显然您可以使用一系列嵌套with patch(...)语句或单个长with语句来做同样的事情。

于 2021-01-10T15:42:12.240 回答