it-swarm.com.de

Wie kann man ein Python-Modul wie Ullib mock/stub machen?

Ich muss eine Funktion testen, die eine Seite auf einem externen Server mit urllib.urlopen abfragen muss (sie verwendet auch urllib.urlencode). Der Server ist möglicherweise inaktiv, die Seite kann sich ändern. Ich kann mich für einen Test nicht darauf verlassen.

Was ist der beste Weg, um zu kontrollieren, was urllib.urlopen zurückgibt?

64
Dinoboff

Ein anderer einfacher Ansatz besteht darin, die Funktion urlopen() von urllib zu überschreiben. Zum Beispiel, wenn Ihr Modul hat

import urllib

def some_function_that_uses_urllib():
    ...
    urllib.urlopen()
    ...

Sie können Ihren Test so definieren:

import mymodule

def dummy_urlopen(url):
    ...

mymodule.urllib.urlopen = dummy_urlopen

Wenn Ihre Tests dann Funktionen in mymodule aufrufen, wird dummy_urlopen() anstelle der eigentlichen urlopen() aufgerufen. Dynamische Sprachen wie Python machen es sehr einfach, Methoden und Klassen zu testen.

Weitere Informationen zum Aushängen von Abhängigkeiten für Tests finden Sie in meinen Blogbeiträgen unter http://softwarecorner.wordpress.com/ .

92
Clint Miller

Ich benutze Mock's Patch Dekorator:

from mock import patch

[...]

@patch('urllib.urlopen')
def test_foo(self, urlopen_mock):
    urlopen_mock.return_value = MyUrlOpenMock()
67
Dinoboff

Hast du Mox geschaut? Es sollte alles tun, was Sie brauchen. Hier ist eine einfache interaktive Sitzung, die die von Ihnen benötigte Lösung veranschaulicht:

>>> import urllib
>>> # check that it works
>>> urllib.urlopen('http://www.google.com/')
<addinfourl at 3082723820L ...>
>>> # check what happens when it doesn't
>>> urllib.urlopen('http://hopefully.doesnotexist.com/')
#-- snip --
IOError: [Errno socket error] (-2, 'Name or service not known')

>>> # OK, let's mock it up
>>> import mox
>>> m = mox.Mox()
>>> m.StubOutWithMock(urllib, 'urlopen')
>>> # We can be verbose if we want to :)
>>> urllib.urlopen(mox.IgnoreArg()).AndRaise(
...   IOError('socket error', (-2, 'Name or service not known')))

>>> # Let's check if it works
>>> m.ReplayAll()
>>> urllib.urlopen('http://www.google.com/')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.5/site-packages/mox.py", line 568, in __call__
    raise expected_method._exception
IOError: [Errno socket error] (-2, 'Name or service not known')

>>> # yay! now unset everything
>>> m.UnsetStubs()
>>> m.VerifyAll()
>>> # and check that it still works
>>> urllib.urlopen('http://www.google.com/')
<addinfourl at 3076773548L ...>
27
Damir Zekić

HTTPretty arbeitet genauso wie FakeWeb. HTTPretty funktioniert in der Socket-Schicht, daher sollte es alle Python-HTTP-Client-Bibliotheken abfangen. Es ist ein Kampf gegen urllib2, httplib2 und Anfragen

import urllib2
from httpretty import HTTPretty, httprettified


@httprettified
def test_one():
    HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/",
                           body="Find the best daily deals")

    fd = urllib2.urlopen('http://yipit.com')
    got = fd.read()
    fd.close()

    assert got == "Find the best daily deals"
14
Gabriel Falcão

Der beste Weg, dies zu tun, ist wahrscheinlich, den Code aufzuteilen, so dass die Logik, die den Seiteninhalt verarbeitet, vom Code getrennt wird, der die Seite abruft.

Übergeben Sie dann eine Instanz des Abrufcodes in die Verarbeitungslogik, die Sie für den Komponententest leicht durch einen Scheinabruf ersetzen können.

z.B.

class Processor(oject):
    def __init__(self, fetcher):
        self.m_fetcher = fetcher

    def doProcessing(self):
        ## use self.m_fetcher to get page contents

class RealFetcher(object):
    def fetchPage(self, url):
        ## get real contents

class FakeFetcher(object):
    def fetchPage(self, url):
        ## Return whatever fake contents are required for this test
8
Douglas Leeder

Falls Sie das Modul nicht einmal laden möchten:

import sys,types
class MockCallable():
  """ Mocks a function, can be enquired on how many calls it received """
  def __init__(self, result):
    self.result  = result
    self._calls  = []

  def __call__(self, *arguments):
    """Mock callable"""
    self._calls.append(arguments)
    return self.result

  def called(self):
    """docstring for called"""
    return self._calls

class StubModule(types.ModuleType, object):
  """ Uses a stub instead of loading libraries """

  def __init__(self, moduleName):
    self.__= moduleName
    sys.modules[moduleName] = self

  def __repr__(self):
    name  = self.__name__
    mocks = ', '.join(set(dir(self)) - set(['__name__']))
    return "<StubModule: %(name)s; mocks: %(mocks)s>" % locals()

class StubObject(object):
  pass

Und dann:

>>> urllib = StubModule("urllib")
>>> import urllib # won't actually load urllib

>>> urls.urlopen = MockCallable(StubObject())

>>> example = urllib.urlopen('http://example.com')
>>> example.read = MockCallable('foo')

>>> print(example.read())
'foo'
8
ilpoldo

Die einfachste Möglichkeit ist, Ihre Funktion so zu ändern, dass sie nicht unbedingt urllib.urlopen verwendet. Nehmen wir an, dies ist Ihre ursprüngliche Funktion:

def my_grabber(arg1, arg2, arg3):
    # .. do some stuff ..
    url = make_url_somehow()
    data = urllib.urlopen(url)
    # .. do something with data ..
    return answer

Fügen Sie ein Argument hinzu, das zum Öffnen der URL verwendet wird. Dann können Sie eine Mock-Funktion bereitstellen, um zu tun, was Sie benötigen:

def my_grabber(arg1, arg2, arg3, urlopen=urllib.urlopen):
    # .. do some stuff ..
    url = make_url_somehow()
    data = urlopen(url)
    # .. do something with data ..
    return answer

def test_my_grabber():
    my_grabber(arg1, arg2, arg3, urlopen=my_mock_open)
3
Ned Batchelder

Um Clint Millers Antwort hinzuzufügen, musste ich dazu eine falsche Klasse erstellen, die eine Lesemethode wie folgt implementiert:

class FakeURL:
    def read(foo):
        return '{"some":"json_text"}'

Dann stülpen Sie urllib2.open aus:

# Stub out urllib2.open.
def dummy_urlopen(foo, bar, baz):
  return FakeURL()
urllib2.urlopen = dummy_urlopen
0
Alex Harvey