2010-06-29 2 views
2

Я пытаюсь проверить код python, который использует urllib2 и lxml.Mocking urllib2.urlopen и lxml.etree.parse using pymox

Я видел несколько сообщений в блогах и столбцы переполнения стека, где люди хотят проверять исключения, с urllib2. Я не видел примеров тестирования успешных звонков.

Я иду по правильному пути?

У кого-нибудь есть предложение, чтобы заставить это работать?

Вот то, что я до сих пор:

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() 

Он терпит неудачу с:

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 

Это происходит потому, что response.read() не возвращает то, что я ожидал, что он вернется.

ответ

4

Я бы не стал вникать в внутренние элементы urllib2. Я думаю, что это выходит за рамки того, что вам нужно. Вот простой способ сделать это с помощью StringIO. Главное здесь - то, что вы намерены анализировать как XML, просто должны быть похожими на файлы с точки зрения печати утиных, это не обязательно будет фактическим экземпляром 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

Спасибо, Питер. Еще один поворот. Что делать, если я также хотел проверить код ответа? Итак, if (response2.getcode() == 200): parse; else: создать исключение. – jmkacz

+0

Я добавил 'response.getcode = lambda: 200' после определения ответа и, похоже, работает. – jmkacz

+0

Хорошо, отлично. Ничто из этого не выиграет никаких наград за элегантность, но оно выполняет свою работу. –

0

Похоже, что ваш отказ не связан с моксом вообще - строка, вызывающая ошибку, считывается из response2, что является прямым вызовом slashdot. Возможно, проверьте этот объект и посмотрите, что это такое?

EDIT: Я не видел линию m.StubOutWithMock(urllib2, 'urlopen') выше, поэтому я думал, что вы сравниваете два звонка; один из насмек (ответ), а другой нет (ответ2). Ниже приведен обновленный ответ.

+0

Если вы посмотрите на urllib.py, self.read устанавливается равным self.fp.read. Эти два вызова должны возвращать одни и те же данные. Из моих комментариев в коде self.fp.read возвращает строку, а self.read возвращает . Это потому, что '__init__' не вызывается в addinfourl, поэтому я добавил назначение метода в свой тестовый код, и он не возвращает ожидаемого. – jmkacz

+0

Что произойдет, если вы определите его явно? то есть. вместо: 'response.read = response.fp.read' Использование:. ' response.read() AndReturn ("" " bar "" ")' Возможно, что мокс делает какую-то магию за кулисами, когда вы вызываете метод .read(). –

+0

Я пробовал это, но вызовы библиотеки низкого уровня читаются с количеством байтов, которое он хочет, поэтому вы не можете действительно издеваться над прочитанным в этом случае. – jmkacz

2

Повторяя, что Петр сказал, я бы просто добавить, что вам не нужно иметь дело с LXML внутренностей больше, чем urllib2. Издеваясь над lxml.etree, вы можете полностью изолировать код, который вам действительно нужно проверить, свой собственный. Вот пример, который делает это, а также показывает, как вы можете использовать макет-объект для проверки вызова 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)) 
Смежные вопросы