IT박스

스크래피 단위 테스트

itboxs 2021. 1. 7. 07:47
반응형

스크래피 단위 테스트


Scrapy (화면 스크레이퍼 / 웹 크롤러)에서 일부 단위 테스트를 구현하고 싶습니다. 프로젝트는 "scrapy crawl"명령을 통해 실행되므로 코와 같은 것을 통해 실행할 수 있습니다. scrapy는 twisted 위에 구축되었으므로 단위 테스트 프레임 워크 Trial을 사용할 수 있습니까? 그렇다면 어떻게? 그렇지 않으면 코를 작동 시키고 싶습니다 .

최신 정보:

나는 Scrapy-Users 에 대해 이야기하고 있는데 "테스트 코드에서 응답을 빌드 한 다음 응답으로 메서드를 호출하고 [내가] 예상되는 항목 / 요청을 출력에 가져왔다"고 주장해야한다고 생각합니다. 그래도 작동하지 않는 것 같습니다.

단위 테스트 테스트 클래스와 테스트를 빌드 할 수 있습니다.

  • 응답 객체 생성
  • 응답 객체를 사용하여 내 스파이더의 구문 분석 메서드를 호출하십시오.

그러나 결국 트레이스 백 을 생성 합니다 . 이유에 대한 통찰력이 있습니까?


내가 한 방법은 가짜 응답을 만드는 것입니다. 이렇게하면 오프라인에서 구문 분석 기능을 테스트 할 수 있습니다. 그러나 실제 HTML을 사용하여 실제 상황을 얻습니다.

이 방법의 문제점은 로컬 HTML 파일이 온라인의 최신 상태를 반영하지 않을 수 있다는 것입니다. 따라서 HTML이 온라인으로 변경되면 큰 버그가있을 수 있지만 테스트 케이스는 여전히 통과합니다. 따라서 이러한 방식으로 테스트하는 가장 좋은 방법이 아닐 수 있습니다.

내 현재 워크 플로는 오류가있을 때마다 URL과 함께 관리자에게 이메일을 보냅니다. 그런 다음 특정 오류에 대해 오류를 일으키는 내용으로 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를 추천합니다.


새로 추가 된 스파이더 계약 은 시도해 볼 가치가 있습니다. 많은 코드를 요구하지 않고 테스트를 추가하는 간단한 방법을 제공합니다.


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

참고로 Ian Cordasco의 강연 덕분에 pycon 2015에서 betamax를 발견했습니다 .


이것은 매우 늦게 대답하지만 내가 쓴 그래서 나는 scrapy 테스트와 짜증 봤는데 scrapy 테스트를 정의 사양에 대해 scrapy 크롤러를 테스트하기위한 프레임 워크입니다.

정적 출력이 아닌 테스트 사양을 정의하여 작동합니다. 예를 들어 이러한 종류의 항목을 크롤링하는 경우 :

{
    "name": "Alex",
    "age": 21,
    "gender": "Female",
}

스크래피 테스트를 정의 할 수 있습니다 ItemSpec.

from scrapytest.tests import Match, MoreThan, LessThan
from scrapytest.spec import ItemSpec

class MySpec(ItemSpec):
    name_test = Match('{3,}')  # name should be at least 3 characters long
    age_test = Type(int), MoreThan(18), LessThan(99)
    gender_test = Match('Female|Male')

스크래피 통계에 대해 다음과 같은 아이디어 테스트도 있습니다 StatsSpec.

from scrapytest.spec import StatsSpec
from scrapytest.tests import Morethan

class MyStatsSpec(StatsSpec):
    validate = {
        "item_scraped_count": MoreThan(0),
    }

나중에 라이브 또는 캐시 된 결과에 대해 실행할 수 있습니다.

$ scrapy-test 
# or
$ scrapy-test --cache

저는 개발 변경을 위해 캐시 된 실행을 실행하고 웹 사이트 변경을 감지하기 위해 매일 cronjob을 실행했습니다.


I'm using scrapy 1.3.0 and the function: fake_response_from_file, raise an error:

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

I get:

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

The solution is to use TextResponse instead, and it works ok, as example:

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

Thanks a lot.


Slightly simpler, by removing the def fake_response_from_file from the chosen answer:

import unittest
from spiders.my_spider import MySpider
from scrapy.selector import Selector


class TestParsers(unittest.TestCase):


    def setUp(self):
        self.spider = MySpider(limit=1)
        self.html = Selector(text=open("some.htm", 'r').read())


    def test_some_parse(self):
        expected = "some-text"
        result = self.spider.some_parse(self.html)
        self.assertEqual(result, expected)


if __name__ == '__main__':
    unittest.main()

You can follow this snippet from the scrapy site to run it from a script. Then you can make any kind of asserts you'd like on the returned items.


I'm using Twisted's trial to run tests, similar to Scrapy's own tests. It already starts a reactor, so I make use of the CrawlerRunner without worrying about starting and stopping one in the tests.

Stealing some ideas from the check and parse Scrapy commands I ended up with the following base TestCase class to run assertions against live sites:

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

Example:

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

참조 URL : https://stackoverflow.com/questions/6456304/scrapy-unit-testing

반응형