Тестирование с помощью Scrapy Unit

Я хотел бы реализовать некоторые модульные тесты в Scrapy (скребок экрана/веб-сканер). Поскольку проект выполняется через команду "scrapy crawl", я могу запустить ее через что-то вроде носа. Поскольку скрипирование построено на вершине скрученного, я могу использовать его модульный модуль тестирования Trial? Если да, то как? В противном случае я бы хотел начать работать носом.

Update:

Я говорил Scrapy-Users, и я предполагаю, что я должен "построить ответ в тестовом коде", а затем вызвать метод с ответом и утверждают, что [I] получают ожидаемые элементы/запросы в выходе ". Кажется, я не могу заставить это работать.

Я могу построить тестовый класс unit-test и в тесте:

  • создать объект ответа
  • попробуйте вызвать метод parse моего паука с объектом ответа

Однако он заканчивает создание этой трассировки. Любое понимание того, почему?

Ответ 1

Как я это сделал, создайте поддельные ответы, таким образом вы можете протестировать функцию анализа в автономном режиме. Но вы получаете реальную ситуацию, используя реальный HTML.

Проблема с этим подходом заключается в том, что ваш локальный HTML файл может не соответствовать последнему состоянию онлайн. Поэтому, если HTML изменяется в Интернете, у вас может быть большая ошибка, но ваши тестовые примеры все равно пройдут. Таким образом, это может быть не лучший способ проверить этот способ.

Мой текущий рабочий процесс, всякий раз, когда есть ошибка, я отправил электронное письмо администратору с URL-адресом. Затем для этой конкретной ошибки я создаю html файл с содержимым, которое вызывает ошибку. Затем я создаю для него unittest.

Это код, который я использую для создания тестовых HTTP-ответов Scrapy для тестирования из локального html файла:

# 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/response/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

Ответ 2

Следует добавить недавно добавленные Пауки-контракты. Это дает вам простой способ добавить тесты, не требуя большого количества кода.

Ответ 3

Я использую Betamax, чтобы запустить тест на реальном сайте в первый раз и сохранить http ответы локально, чтобы следующие тесты выполнялись супер быстро после:

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

FYI, я обнаруживаю бетамакс на pycon 2015 благодаря Ian Cordasco говорить.

Ответ 4

Вы можете выполнить этот фрагмент с сайта scrapy, чтобы запустить его из script. Затем вы можете сделать любые утверждения, которые вы хотели бы получить по возвращенным элементам.

Ответ 5

Я использую scrapy 1.3.0 и функцию: fake_response_from_file, вызывают ошибку:

response = Response(url=url, request=request, body=file_content)

Я получаю:

raise AttributeError("Response content isn't text")

Решение заключается в использовании TextResponse вместо этого, и оно работает нормально, как пример:

response = TextResponse(url=url, request=request, body=file_content)     

Большое спасибо.

Ответ 6

Я использую Twisted trial для запуска тестов, аналогичных собственным тестам Scrapy. Он уже запускает реактор, поэтому я использую CrawlerRunner, не беспокоясь о запуске и остановке одного в тестах.

Краду некоторые идеи из команд check и parse Scrapy, в результате я получил следующий базовый класс TestCase для запуска утверждений против живых сайтов:

from twisted.trial import unittest

from scrapy.crawler import CrawlerRunner
from scrapy.http import Request
from scrapy.item import BaseItem
from scrapy.utils.spider import iterate_spider_output

class SpiderTestCase(unittest.TestCase):
    def setUp(self):
        self.runner = CrawlerRunner()

    def make_test_class(self, cls, url):
        """
        Make a class that proxies to the original class,
        sets up a URL to be called, and gathers the items
        and requests returned by the parse function.
        """
        class TestSpider(cls):
            # This is a once used class, so writing into
            # the class variables is fine. The framework
            # will instantiate it, not us.
            items = []
            requests = []

            def start_requests(self):
                req = super(TestSpider, self).make_requests_from_url(url)
                req.meta["_callback"] = req.callback or self.parse
                req.callback = self.collect_output
                yield req

            def collect_output(self, response):
                try:
                    cb = response.request.meta["_callback"]
                    for x in iterate_spider_output(cb(response)):
                        if isinstance(x, (BaseItem, dict)):
                            self.items.append(x)
                        elif isinstance(x, Request):
                            self.requests.append(x)
                except Exception as ex:
                    print("ERROR", "Could not execute callback: ",     ex)
                    raise ex

                # Returning any requests here would make the     crawler follow them.
                return None

        return TestSpider

Пример:

@defer.inlineCallbacks
def test_foo(self):
    tester = self.make_test_class(FooSpider, 'https://foo.com')
    yield self.runner.crawl(tester)
    self.assertEqual(len(tester.items), 1)
    self.assertEqual(len(tester.requests), 2)

или выполнить один запрос в настройке и выполнить несколько тестов с результатами:

@defer.inlineCallbacks
def setUp(self):
    super(FooTestCase, self).setUp()
    if FooTestCase.tester is None:
        FooTestCase.tester = self.make_test_class(FooSpider, 'https://foo.com')
        yield self.runner.crawl(self.tester)

def test_foo(self):
    self.assertEqual(len(self.tester.items), 1)