Scrapy单元测试
我想在Scrapy(一个用于抓取网页的工具)中实现一些单元测试。因为项目是通过“scrapy crawl”命令来运行的,所以我可以用像nose这样的工具来运行它。由于Scrapy是建立在twisted之上的,我可以使用它的单元测试框架Trial吗?如果可以的话,怎么做?如果不行,我希望能让nose正常工作。
更新:
我在Scrapy-Users上讨论过,我想我应该在测试代码中“构建响应,然后用这个响应调用方法,并检查输出中是否得到了预期的项目/请求”。不过我似乎无法做到这一点。
我可以创建一个单元测试类,并在测试中:
- 创建一个响应对象
- 尝试用这个响应对象调用我爬虫的解析方法
但是最终生成了这个错误追踪信息。有人能告诉我为什么吗?
10 个回答
新增加的 蜘蛛合约 值得一试。它为你提供了一种简单的方法来添加测试,而不需要写很多代码。
我使用Betamax来第一次在真实网站上进行测试,并把HTTP响应保存在本地,这样下次测试就能非常快速地运行:
Betamax会拦截你发出的每一个请求,并试图找到一个已经被拦截并记录下来的匹配请求。
当你需要获取网站的最新版本时,只需删除Betamax记录的内容,然后重新运行测试。
示例:
from scrapy import Spider, Request
from scrapy.http import HtmlResponse
class Example(Spider):
name = 'example'
url = 'http://doc.scrapy.org/en/latest/_static/selectors-sample1.html'
def start_requests(self):
yield Request(self.url, self.parse)
def parse(self, response):
for href in response.xpath('//a/@href').extract():
yield {'image_href': href}
# Test part
from betamax import Betamax
from betamax.fixtures.unittest import BetamaxTestCase
with Betamax.configure() as config:
# where betamax will store cassettes (http responses):
config.cassette_library_dir = 'cassettes'
config.preserve_exact_body_bytes = True
class TestExample(BetamaxTestCase): # superclass provides self.session
def test_parse(self):
example = Example()
# http response is recorded in a betamax cassette:
response = self.session.get(example.url)
# forge a scrapy response to test
scrapy_response = HtmlResponse(body=response.content, url=example.url)
result = example.parse(scrapy_response)
self.assertEqual({'image_href': u'image1.html'}, result.next())
self.assertEqual({'image_href': u'image2.html'}, result.next())
self.assertEqual({'image_href': u'image3.html'}, result.next())
self.assertEqual({'image_href': u'image4.html'}, result.next())
self.assertEqual({'image_href': u'image5.html'}, result.next())
with self.assertRaises(StopIteration):
result.next()
顺便说一下,我是在2015年的pycon上通过Ian Cordasco的演讲发现Betamax的。
我做的方法是创建一些假数据,这样你就可以在离线状态下测试解析功能。不过,使用真实的HTML可以让你更好地了解实际情况。
这种方法的问题在于,你本地的HTML文件可能无法反映网上最新的状态。如果网上的HTML发生了变化,你可能会遇到大问题,但你的测试用例仍然会通过。所以,这种测试方式可能不是最好的选择。
我现在的工作流程是,每当出现错误时,我会给管理员发一封邮件,里面包含出错的链接。然后,我会为这个特定的错误创建一个包含导致错误内容的HTML文件。接着,我会为它写一个单元测试。
这是我用来从本地HTML文件创建Scrapy HTTP响应样本进行测试的代码:
# scrapyproject/tests/responses/__init__.py
import os
from scrapy.http import Response, Request
def fake_response_from_file(file_name, url=None):
"""
Create a Scrapy fake HTTP response from a HTML file
@param file_name: The relative filename from the responses directory,
but absolute paths are also accepted.
@param url: The URL of the response.
returns: A scrapy HTTP response which can be used for unittesting.
"""
if not url:
url = 'http://www.example.com'
request = Request(url=url)
if not file_name[0] == '/':
responses_dir = os.path.dirname(os.path.realpath(__file__))
file_path = os.path.join(responses_dir, file_name)
else:
file_path = file_name
file_content = open(file_path, 'r').read()
response = Response(url=url,
request=request,
body=file_content)
response.encoding = 'utf-8'
return response
样本HTML文件位于scrapyproject/tests/responses/osdir/sample.html
然后测试用例可能看起来像这样: 测试用例的位置是scrapyproject/tests/test_osdir.py
import unittest
from scrapyproject.spiders import osdir_spider
from responses import fake_response_from_file
class OsdirSpiderTest(unittest.TestCase):
def setUp(self):
self.spider = osdir_spider.DirectorySpider()
def _test_item_results(self, results, expected_length):
count = 0
permalinks = set()
for item in results:
self.assertIsNotNone(item['content'])
self.assertIsNotNone(item['title'])
self.assertEqual(count, expected_length)
def test_parse(self):
results = self.spider.parse(fake_response_from_file('osdir/sample.html'))
self._test_item_results(results, 10)
这基本上就是我测试解析方法的方式,但不仅限于解析方法。如果变得更复杂,我建议看看 Mox