37

我正在尝试测试一个为一些 Web 服务提供接口的包。它有一个测试套件,可以在连接互联网的情况下测试大多数功能。但是,有一些挥之不去的测试可能会尝试连接到互联网/下载数据,我想阻止它们这样做有两个原因:首先,确保我的测试套件在没有可用网络连接的情况下工作;其次,这样我就不会使用过多的查询向 Web 服务发送垃圾邮件。

一个明显的解决方案是拔下我的机器/关闭无线,但是当我在远程机器上运行测试时显然不起作用。

所以,我的问题是:我可以阻止单个 python 进程的网络/端口访问吗?(“沙盒”它,但只是阻止网络连接)

(afaict,pysandbox 不这样做)

编辑:我正在使用py.test,所以我需要一个可以使用的解决方案,py.test以防影响任何建议的答案。

4

6 回答 6

36

猴子补丁socket应该这样做:

import socket
def guard(*args, **kwargs):
    raise Exception("I told you not to use the Internet!")
socket.socket = guard

确保它在任何其他导入之前运行。

于 2013-09-03T21:35:46.523 回答
21

更新:现在有一个 pytest 插件与这个答案做同样的事情!您可以阅读答案只是为了了解事情是如何工作的,但我强烈建议使用插件而不是复制粘贴我的答案:-) 请参见此处:https ://github.com/miketheman/pytest-socket


我发现 Thomas Orozco 的回答非常有帮助。继 keflavich 之后,这就是我集成到我的单元测试套件中的方式。这对我来说适用于数千个非常不同的单元测试用例(<100 需要套接字)......以及进出 doctests。

我张贴在这里。为方便起见,包括以下内容。使用 Python 2.7.5 测试,pytest==2.7.0。(要自己测试,请py.test --doctest-modules在克隆了所有 3 个文件的目录中运行。)

_socket_toggle.py

from __future__ import print_function
import socket
import sys

_module = sys.modules[__name__]

def disable_socket():
    """ disable socket.socket to disable the Internet. useful in testing.

    .. doctest::
        >>> enable_socket()
        [!] socket.socket is enabled.
        >>> disable_socket()
        [!] socket.socket is disabled. Welcome to the desert of the real.
        >>> socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        Traceback (most recent call last):
        ...
        RuntimeError: I told you not to use the Internet!
        >>> enable_socket()
        [!] socket.socket is enabled.
        >>> enable_socket()
        [!] socket.socket is enabled.
        >>> disable_socket()
        [!] socket.socket is disabled. Welcome to the desert of the real.
        >>> socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        Traceback (most recent call last):
        ...
        RuntimeError: I told you not to use the Internet!
        >>> enable_socket()
        [!] socket.socket is enabled.
    """
    setattr(_module, '_socket_disabled', True)

    def guarded(*args, **kwargs):
        if getattr(_module, '_socket_disabled', False):
            raise RuntimeError("I told you not to use the Internet!")
        else:
            # SocketType is a valid public alias of socket.socket,
            # we use it here to avoid namespace collisions
            return socket.SocketType(*args, **kwargs)

    socket.socket = guarded

    print(u'[!] socket.socket is disabled. Welcome to the desert of the real.')


def enable_socket():
    """ re-enable socket.socket to enable the Internet. useful in testing.
    """
    setattr(_module, '_socket_disabled', False)
    print(u'[!] socket.socket is enabled.')

conftest.py

# Put this in the conftest.py at the top of your unit tests folder,
# so it's available to all unit tests
import pytest
import _socket_toggle


def pytest_runtest_setup():
    """ disable the interet. test-cases can explicitly re-enable """
    _socket_toggle.disable_socket()


@pytest.fixture(scope='function')
def enable_socket(request):
    """ re-enable socket.socket for duration of this test function """
    _socket_toggle.enable_socket()
    request.addfinalizer(_socket_toggle.disable_socket)

test_example.py

# Example usage of the py.test fixture in tests
import socket
import pytest

try:
    from urllib2 import urlopen
except ImportError:
    import urllib3
    urlopen = urllib.request.urlopen


def test_socket_disabled_by_default():
    # default behavior: socket.socket is unusable
    with pytest.raises(RuntimeError):
        urlopen(u'https://www.python.org/')


def test_explicitly_enable_socket(enable_socket):
    # socket is enabled by pytest fixture from conftest. disabled in finalizer
    assert socket.socket(socket.AF_INET, socket.SOCK_STREAM)
于 2015-05-05T23:01:37.610 回答
1

建立在 Thomas Orozco 和driftcatcher 非常有用的答案的基础上,这里是一个与 Python 的 unittest 和(经过小幅改动)Django 一起工作的变体。

您需要做的就是从增强类继承您的测试用例NoSocketTestCase类,任何对网络的访问都会被检测到并引发SocketAccessError异常。

这种方法也适用于 Django。您只需要将NoSocketTestCase类更改为继承自django.test.TestCase而不是unittest.TestCase.

虽然没有严格回答 OP 的问题,但我认为这可能对任何想要在单元测试中阻止网络访问的人有所帮助。

no_sockets.py

import socket
from unittest import TestCase


class SocketAccessError(Exception):
    pass


class NoSocketsTestCase(TestCase):
    """Enhancement of TestCase class that prevents any use of sockets

    Will throw the exception SocketAccessError when any code tries to
    access network sockets
    """

    @classmethod
    def setUpClass(cls):
        cls.socket_original = socket.socket
        socket.socket = cls.guard
        return super().setUpClass()

    @classmethod
    def tearDownClass(cls):
        socket.socket = cls.socket_original
        return super().tearDownClass()

    @staticmethod
    def guard(*args, **kwargs):
        raise SocketAccessError('Attempted to access network')

test_no_sockets.py

import urllib.request
from .no_sockets import NoSocketsTestCase, SocketAccessError


class TestNoSocketsTestCase(NoSocketsTestCase):

    def test_raises_exception_on_attempted_network_access(self):

        with self.assertRaises(SocketAccessError):            
            urllib.request.urlopen('https://www.google.com')

于 2020-04-04T11:44:40.473 回答
1

一个简单的方法在库上放个噱头requests

from unittest import mock

requests_gag = mock.patch(
    'requests.Session.request',
    mock.Mock(side_effect=RuntimeError(
        'Please use the `responses` library to mock HTTP in your tests.'
    ))
)

with requests_gag:
    ...  # no Internet here


于 2019-12-26T12:20:50.697 回答
0

我有一个 pytest 解决方案。pytest-network图书馆帮我解决这个问题。

# conftest.py
import pytest
import socket

_original_connect = socket.socket.connect

def patched_connect(*args, **kwargs):
    ...
    # It depends on your testing purpose
    # You may want a exception, add here
    # If you test unconnectable situations
    # it can stay like this 
    

@pytest.fixture
def enable_network():
    socket.socket.connect = _original_connect
    yield
    socket.socket.connect = patched_connect

@pytest.fixture
def disable_network():
    socket.socket.connect = patched_connect
    yield
    socket.socket.connect = _original_connect
# test_internet.py
def test_your_unconnectable_situation(disable_network):
    response = request.get('http://stackoverflow.com/')
    response.status_code == 400
于 2021-09-02T08:33:01.897 回答
0

httpretty是一个解决这个问题的小库。

如果您使用的是 Django 测试运行程序,请编写一个自定义测试运行程序,在其中禁用所有 3rd 方 API 调用。

# common/test_runner.py

import httpretty
from django.test.runner import DiscoverRunner


class CustomTestRunner(DiscoverRunner):
    def run_tests(self, *args, **kwargs):
        with httpretty.enabled(allow_net_connect=False):
            return super().run_tests(*args, **kwargs)

将此新测试运行器添加到您的设置中

TEST_RUNNER = "common.test_runner.CustomTestRunner"

从现在开始,所有外部 API 调用都必须被模拟或httpretty.errors.UnmockedError将被引发。

如果您使用的是 pytest,这个夹具应该可以工作。

@pytest.fixture
def disable_external_api_calls():
    httpretty.enable()
    yield
    httpretty.disable()
于 2021-08-18T20:30:41.897 回答