为scrapy CrawlSpider方法创建单元测试

时间:2016-04-28 14:26:52

标签: python unit-testing mocking scrapy

最初的问题

我正在编写一个CrawlSpider类(使用scrapy库)并依赖大量scrapy 异步魔法来使其工作。在这里,它被剥离了:

class MySpider(CrawlSpider):
    rules = [Rule(LinkExtractor(allow='myregex'), callback='parse_page')]
    # some other class attributes

    def __init__(self, *args, **kwargs):
        super(MySpider, self).__init__(*args, **kwargs)
        self.response = None
        self.loader = None

    def parse_page_section(self):
        soup = BeautifulSoup(self.response.body, 'lxml')
        # Complicated scraping logic using BeautifulSoup
        self.loader.add_value(mykey, myvalue)

    # more methods parsing other sections of the page
    # also using self.response and self.loader

    def parse_page(self, response):
        self.response = response
        self.loader = ItemLoader(item=Item(), response=response)
        self.parse_page_section()
        # call other methods to collect more stuff
        self.loader.load_item()

类属性rule告诉我的蜘蛛关注某些链接并在下载网页后跳转到回调函数。我的目标是测试名为parse_page_section的解析方法,而无需运行爬虫或甚至发出真正的HTTP请求。

我尝试了什么

本能地,我转向mock库。我理解你如何模拟一个函数来测试它是否被调用(使用哪些参数以及是否有任何副作用......),但这不是我想要的。我想实例化一个假对象MySpider并分配足够的属性,以便能够在其上调用parse_page_section方法。

在上面的示例中,我需要一个response对象来实例化我的ItemLoader,特别是self.response.body属性来实例化我的BeautifulSoup。原则上,我可以制作这样的假物品:

from argparse import Namespace

my_spider = MySpider(CrawlSpider)
my_spider.response = NameSpace(body='<html>...</html>')

这适用于BeautifulSoup类,但我需要添加更多属性来创建ItemLoader对象。对于更复杂的情况,它会变得丑陋且无法管理。

我的问题

这是完全正确的方法吗?我无法在网上找到类似的例子,所以我认为我的方法可能在更基础的层面上是错误的。任何见解将不胜感激。

1 个答案:

答案 0 :(得分:2)

你见过Spiders Contracts吗?

这使您可以测试蜘蛛的每个回调,而无需大量代码。例如:

def parse(self, response):
    """ This function parses a sample response. Some contracts are mingled
    with this docstring.

    @url http://www.amazon.com/s?field-keywords=selfish+gene
    @returns items 1 16
    @returns requests 0 0
    @scrapes Title Author Year Price
    """

使用check命令运行合同检查。

如果你想要更大的东西,请看这个answer