2013-09-03 9 views
11

Ich versuche ein Paket zu testen, das Schnittstellen zu einigen Webdiensten bereitstellt. Es hat eine Testsuite, die die meisten Funktionen testen soll ohne Verbindung mit dem Internet. Allerdings gibt es einige andauernde Tests, die versuchen könnten, eine Verbindung zu den Internet/Download-Daten herzustellen, und ich möchte sie aus zwei Gründen daran hindern: Erstens, um sicherzustellen, dass meine Testsuite funktioniert, wenn keine Netzwerkverbindung verfügbar ist; zweitens, damit ich die Webdienste nicht mit übermäßigen Anfragen spammele.Python: blockieren Netzwerkverbindungen für Testzwecke?

Eine offensichtliche Lösung ist, mein Gerät zu trennen/WLAN zu deaktivieren, aber wenn ich Tests auf einem Remote-Computer ausführen, funktioniert das offensichtlich nicht.

Also, meine Frage: Kann ich Netzwerk/Port-Zugriff für einen einzigen Python-Prozess blockieren? („Sandbox“, aber nur blockierende Netzwerkverbindungen)

(AFAICT, hat pysandbox dies nicht tun)

EDIT: Ich verwende py.test so brauche ich eine Lösung, die mit py.test, falls arbeiten das betrifft alle vorgeschlagenen Antworten.

Antwort

10

Affe Patchen socket sollte es tun:

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

Stellen Sie sicher, dies läuft vor jedem anderen importieren.

+0

Das ist großartig! Irgendwelche Gedanken darüber, wie man py.test dazu bringt, dies vor allem anderen zu tun? – keflavich

+0

Antwort auf meinen letzten Kommentar: Führen Sie dies in 'confests.py'. – keflavich

+0

@keflavich Gut zu wissen - danke! –

7

Update: Es gibt jetzt ein Pytest-Plugin, das das Gleiche wie diese Antwort tut! Sie können die Antwort lesen, um zu sehen, wie die Dinge funktionieren, aber ich empfehlen dringend, das Plugin anstelle des Kopierens-Einfügen meine Antwort :-) Siehe hier: https://github.com/miketheman/pytest-socket


I Thomas Orozco Antwort gefunden sehr zu sein hilfreich. Im Anschluss an Keflavich habe ich so in meine Unit-Testsuite integriert. Das funktioniert für mich mit Tausenden von sehr unterschiedlichen Unit-Testfällen (< 100, die Sockets benötigen) ... und in und aus Doctests.

Ich postete here. Einschließlich unten für Bequemlichkeit. Getestet mit Python 2.7.5, Pytest == 2.7.0. (Um zu testen, für sich selbst, laufen py.test --doctest-modules im Verzeichnis mit allen 3 Dateien geklont.)

_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) 
+0

Warum nicht eine "ConnectionError" Ausnahme auslösen? –

+0

@FemtoTrader Ich nehme an, das ist _too_ korrekt. Ich möchte den Fehler, den wir werfen, nicht mit dem Fehler verwechseln, den ein eingebauter Fehler für einen legitimen 'ConnectionError' auslösen würde. In der Praxis verwende ich tatsächlich eine Unterklasse von Runtime Error, aber ich wollte dieses Beispiel einfacher halten – hangtwenty

+2

Eine Chance auf ein Update für Python3? Ich würde auch gerne dazu beitragen, dies zu einem pytest Plugin zu machen. –