2

我是一名网络工程师,正在尝试编写一些 Python,所以每天都在学习。我对单元测试和 Pytest 覆盖率报告有疑问。我想我理解单元测试和pytest的概念。

我有一个使用套接字进行 DNS 查找的函数

def get_ip(d):
    """Returns the first IPv4 address that resolves from 'd'

    Args:
        d (string): string that needs to be resolved in DNS.

    Returns:
        string: IPv4 address if found
    """
    logger.info("Returns IP address from hostname: " + d)
    try:
        data = socket.gethostbyname(d)
        ip_addr = repr(data).strip("'")
        logger.debug("IP Address Resolved to: " + ip_addr)
        return ip_addr
    except socket.gaierror:
        return False

我写了一个通过良好的单元测试。我正在使用 pytest-mock 来处理用于 DNS 查找的套接字模拟。Side Effect 似乎在嘲笑 Exception 并且我将 return_value 设置为 False 并且我假设我已经断言返回 False ,测试通过了 ok 这就是为什么我假设我的测试是好的。

import pytest
import pytest_mock
import socket
from utils import network_utils

@pytest.fixture
def test_setup_network_utils():
    return network_utils

def test_get_ip__unhappy(mocker, test_setup_network_utils):
    mocker.patch.object(network_utils, 'socket')
    test_setup_network_utils.socket.gethostbyname.side_effect = socket.gaierror
    with pytest.raises(Exception):
        d = 'xxxxxx'
        test_setup_network_utils.socket.gethostbyname.return_value = False
        test_setup_network_utils.get_ip(d)
    assert not test_setup_network_utils.socket.gethostbyname.return_value
    test_setup_network_utils.socket.gethostbyname.assert_called_once()
    test_setup_network_utils.socket.gethostbyname.assert_called_with(d)

pytest-cov 报告显示 return False 行未被覆盖。

pytest --cov-report term-missing:skip-covered --cov=utils unit_tests

network_utils.py     126      7    94%   69

第 69 行是函数中的以下代码行

except socket.gaierror:
    return False <<<<<<<< This is missing from the report

任何指针都将不胜感激,为什么 False 的返回没有被声明为已覆盖。就像我上面说的,我对 Python 和编码很陌生,这是我在 Stackoverflow 上的第一个问题。希望我已经解释了我的问题并提供了足够的信息以获得一些指导。

4

1 回答 1

1

当你声明

mocker.patch.object(network_utils, 'socket')

socket您正在用一个模拟替换整个模块,因此socket模块中的所有内容也都变成了模拟,包括socket.gaierror. 因此,在运行测试时尝试捕获socket.gaierror时,Python 会抱怨它不是异常(不从 子类化BaseException)并失败。因此,不执行所需的返回行。

总体而言,您的测试看起来设计过度,并且包含许多不必要的代码。你需要什么test_setup_network_utils?为什么在测试中捕获任意异常?test_get_ip__unhappy涵盖案例中完整代码的重新访问gaierror

def test_get_ip__unhappy(mocker):
    # test setup: patch socket.gethostbyname so it always raises
    mocker.patch('spam.socket.gethostbyname')
    spam.socket.gethostbyname.side_effect = socket.gaierror

    # run test
    d = 'xxxxxx'
    result = spam.get_ip(d)

    # perform checks
    assert result is False
    spam.socket.gethostbyname.assert_called_once()
    spam.socket.gethostbyname.assert_called_with(d)

当然,spam只是一个例子;用您的实际导入替换它。

于 2021-04-27T08:38:44.607 回答