2010-06-29 6 views
2

J'essaie de tester du code python qui utilise urllib2 et lxml.Mocking urllib2.urlopen et lxml.etree.parse en utilisant pymox

J'ai vu plusieurs articles de blog et des messages de débordement de pile où les gens veulent tester les exceptions lancées, avec urllib2. Je n'ai pas vu d'exemples testant des appels réussis.

Est-ce que je descends le bon chemin?

Quelqu'un at-il une suggestion pour que cela fonctionne?

Voici ce que j'ai jusqu'à présent:

import mox 
import urllib 
import urllib2 
import socket 
from lxml import etree 

# set up the test 
m = mox.Mox() 
response = m.CreateMock(urllib.addinfourl) 
response.fp = m.CreateMock(socket._fileobject) 
response.name = None # Needed because the file name is checked. 
response.fp.read().AndReturn("""<?xml version="1.0" encoding="utf-8"?> 
<foo>bar</foo>""") 
response.geturl().AndReturn("http://rss.slashdot.org/Slashdot/slashdot") 
response.read = response.fp.read # Needed since __init__ is not called on addinfourl. 
m.StubOutWithMock(urllib2, 'urlopen') 
urllib2.urlopen(mox.IgnoreArg(), timeout=10).AndReturn(response) 
m.ReplayAll() 

# code under test 
response2 = urllib2.urlopen("http://rss.slashdot.org/Slashdot/slashdot", timeout=10) 
# Note: response2.fp.read() and response2.read() do not behave the same, as defined above. 
# In [21]: response2.fp.read() 
# Out[21]: '<?xml version="1.0" encoding="utf-8"?>\n<foo>bar</foo>' 
# In [22]: response2.read() 
# Out[22]: <mox.MockMethod object at 0x97f326c> 
xcontent = etree.parse(response2) 

# verify test 
m.VerifyAll() 

Il échoue avec:

Traceback (most recent call last): 
    File "/home/jon/mox_question.py", line 22, in <module> 
    xcontent = etree.parse(response2) 
    File "lxml.etree.pyx", line 2583, in lxml.etree.parse (src/lxml/lxml.etree.c:25057) 
    File "parser.pxi", line 1487, in lxml.etree._parseDocument (src/lxml/lxml.etree.c:63708) 
    File "parser.pxi", line 1517, in lxml.etree._parseFilelikeDocument (src/lxml/lxml.etree.c:63999) 
    File "parser.pxi", line 1400, in lxml.etree._parseDocFromFilelike (src/lxml/lxml.etree.c:62985) 
    File "parser.pxi", line 990, in lxml.etree._BaseParser._parseDocFromFilelike (src/lxml/lxml.etree.c:60508) 
    File "parser.pxi", line 542, in lxml.etree._ParserContext._handleParseResultDoc (src/lxml/lxml.etree.c:56659) 
    File "parser.pxi", line 624, in lxml.etree._handleParseResult (src/lxml/lxml.etree.c:57472) 
    File "lxml.etree.pyx", line 235, in lxml.etree._ExceptionContext._raise_if_stored (src/lxml/lxml.etree.c:6222) 
    File "parser.pxi", line 371, in lxml.etree.copyToBuffer (src/lxml/lxml.etree.c:55252) 
TypeError: reading from file-like objects must return byte strings or unicode strings 

C'est parce que response.read() ne retourne pas ce que je m'y attendais pour revenir.

Répondre

4

Je ne voudrais pas plonger dans les internels urllib2 du tout. Cela dépasse la portée de ce que vous pensez. Voici un moyen simple de le faire avec StringIO. La chose clé ici est que ce que vous avez l'intention d'analyser en XML doit juste ressembler à un fichier en termes de dactylographie, il n'a pas besoin d'être une véritable instance addinfourl.

import StringIO 
import mox 
import urllib2 
from lxml import etree 

# set up the test 
m = mox.Mox() 
response = StringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?> 
<foo>bar</foo>""") 
m.StubOutWithMock(urllib2, 'urlopen') 
urllib2.urlopen(mox.IgnoreArg(), timeout=10).AndReturn(response) 
m.ReplayAll() 

# code under test 
response2 = urllib2.urlopen("http://rss.slashdot.org/Slashdot/slashdot", timeout=10) 
xcontent = etree.parse(response2) 

# verify test 
m.VerifyAll() 
+0

Merci Peter. Une autre torsion. Et si je voulais aussi vérifier le code de réponse? Donc, si (response2.getcode() == 200): parse; else: déclenche une exception. – jmkacz

+0

J'ai ajouté 'response.getcode = lambda: 200' après avoir défini la réponse, et cela semble fonctionner. – jmkacz

+0

OK, super. Rien de tout cela ne va gagner des prix pour l'élégance, mais il fait le travail. –

0

Il semble que votre erreur ne soit pas du tout liée à mox - la ligne qui provoque l'erreur est la lecture de response2, qui est un appel direct à slashdot. Peut-être inspecter cet objet et voir ce que c'est le contenu est?

EDIT: Je n'ai pas vu la ligne m.StubOutWithMock(urllib2, 'urlopen') ci-dessus, donc je pensais que vous compariez deux appels; un moqué (réponse) et un non (réponse2). Une réponse mise à jour est ci-dessous.

+0

Si vous regardez urllib.py, self.read est égal à self.fp.read. Ces deux appels doivent renvoyer les mêmes données. De mes commentaires dans le code, self.fp.read renvoie une chaîne, tandis que self.read retourne . C'est parce que '__init__' n'est pas appelé sur addinfourl, j'ai donc ajouté l'assignation de méthode dans mon code de test, et cela ne retourne pas ce que j'attendais. – jmkacz

+0

Que se passe-t-il si vous le définissez explicitement? c'est à dire. au lieu de: 'response.read = response.fp.read' utilisation. ' response.read() andReturn ("" "? Il est possible que mox fasse une sorte de magie derrière les scènes lorsque vous appelez la méthode. –

+0

Je l'avais essayé, mais les appels de bibliothèque de bas niveau lisent avec le nombre d'octets qu'il veut, donc vous ne pouvez pas vraiment mock out lire dans ce cas. – jmkacz

2

Faisant écho à ce que Pierre a dit, je voudrais simplement ajouter que vous ne pouvez pas besoin de se préoccuper de lxml internes, pas plus que ceux de urllib2. En se moquant de lxml.etree, vous pouvez totalement isoler le code que vous avez vraiment besoin de tester, le vôtre. Voici un exemple qui fait cela, et montre également comment vous pouvez utiliser un objet simulé pour tester l'appel de response.getcode().

import mox 
from lxml import etree 
import urllib2 

class TestRssDownload(mox.MoxTestBase): 

    def test_rss_download(self): 
     expected_response = self.mox.CreateMockAnything() 
     self.mox.StubOutWithMock(urllib2, 'urlopen') 
     self.mox.StubOutWithMock(etree, 'parse') 
     self.mox.StubOutWithMock(etree, 'iterwalk') 
     title_elem = self.mox.CreateMock(etree._Element) 
     title_elem.text = 'some title' 

     # Set expectations 
     urllib2.urlopen("http://rss.slashdot.org/Slashdot/slashdot", timeout=10).AndReturn(expected_response) 
     expected_response.getcode().AndReturn(200) 
     etree.parse(expected_response).AndReturn('some parsed content') 
     etree.iterwalk('some parsed content', tag='{http://purl.org/rss/1.0/}title').AndReturn([('end', title_elem),]) 

     # Code under test 
     self.mox.ReplayAll() 
     self.production_code() 

    def production_code(self): 
     response = urllib2.urlopen("http://rss.slashdot.org/Slashdot/slashdot", timeout=10) 
     response_code = response.getcode() 
     if 200 != response_code: 
      raise Exception('Houston, we have a problem ({0})'.format(response_code)) 
     tree = etree.parse(response) 
     for ev, elem in etree.iterwalk(tree, tag='{http://purl.org/rss/1.0/}title'): 
      # Do something with elem.text 
      print('{0}: {1}'.format(ev, elem.text)) 
Questions connexes