Scrapy单元测试

77 投票
10 回答
31826 浏览
提问于 2025-04-16 20:09

我想在Scrapy(一个用于抓取网页的工具)中实现一些单元测试。因为项目是通过“scrapy crawl”命令来运行的,所以我可以用像nose这样的工具来运行它。由于Scrapy是建立在twisted之上的,我可以使用它的单元测试框架Trial吗?如果可以的话,怎么做?如果不行,我希望能让nose正常工作。

更新:

我在Scrapy-Users上讨论过,我想我应该在测试代码中“构建响应,然后用这个响应调用方法,并检查输出中是否得到了预期的项目/请求”。不过我似乎无法做到这一点。

我可以创建一个单元测试类,并在测试中:

  • 创建一个响应对象
  • 尝试用这个响应对象调用我爬虫的解析方法

但是最终生成了这个错误追踪信息。有人能告诉我为什么吗?

10 个回答

24

新增加的 蜘蛛合约 值得一试。它为你提供了一种简单的方法来添加测试,而不需要写很多代码。

27

我使用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的。

83

我做的方法是创建一些假数据,这样你就可以在离线状态下测试解析功能。不过,使用真实的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

撰写回答