2011-06-23 53 views
41

Tôi muốn thực hiện một số kiểm tra đơn vị trong một Scrapy (trình thu thập dữ liệu màn hình/trình thu thập dữ liệu web). Kể từ khi một dự án được chạy thông qua lệnh "thu thập thông tin", tôi có thể chạy nó thông qua một cái gì đó như mũi. Kể từ khi cạo được xây dựng trên đầu trang của xoắn tôi có thể sử dụng thử nghiệm đơn vị khuôn khổ thử nghiệm của nó? Nếu vậy, làm thế nào? Nếu không, tôi muốn nhận được mũi hoạt động.Thử nghiệm đơn vị sơ bộ

Cập nhật:

Tôi đã nói chuyện trên Scrapy-Users và tôi đoán tôi phải "xây dựng đáp ứng trong mã kiểm tra, và sau đó gọi phương thức với phản ứng và khẳng định rằng [I] được các mục/yêu cầu dự kiến ​​trong đầu ra ". Tôi dường như không thể làm được điều này.

tôi có thể xây dựng một lớp thử nghiệm đơn vị thử nghiệm và trong một thử nghiệm:

  • tạo ra một đối tượng phản ứng
  • cố gắng để gọi phương thức phân tích cú pháp của nhện của tôi với các đối tượng phản ứng

Tuy nhiên nó kết thúc tạo ra this truy nguyên. Bất kỳ cái nhìn sâu sắc nào về lý do tại sao?

Trả lời

44

Cách tôi đã thực hiện nó là tạo phản hồi giả, theo cách này bạn có thể kiểm tra hàm phân tích ngoại tuyến. Nhưng bạn nhận được tình hình thực tế bằng cách sử dụng HTML thực.

Sự cố với phương pháp này là tệp HTML cục bộ của bạn có thể không phản ánh trạng thái trực tuyến mới nhất. Vì vậy, nếu HTML thay đổi trực tuyến, bạn có thể có một lỗi lớn, nhưng các trường hợp kiểm tra của bạn vẫn sẽ vượt qua. Vì vậy, nó có thể không phải là cách tốt nhất để thử nghiệm theo cách này.

Luồng công việc hiện tại của tôi là, bất cứ khi nào có lỗi, tôi sẽ gửi email tới quản trị viên, với url. Sau đó, với lỗi cụ thể đó, tôi tạo một tệp html với nội dung gây ra lỗi. Sau đó, tôi tạo ra một unittest cho nó.

Đây là mã tôi sử dụng để tạo ra phản ứng mẫu Scrapy http để thử nghiệm từ một file html địa phương:

# 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 

Các tập tin html mẫu nằm ở scrapyproject/kiểm tra/phản ứng/osdir/sample.html

sau đó testcase có thể nhìn như sau: vị trí trường hợp thử nghiệm là scrapyproject/kiểm tra/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) 

Đó là basica lly cách tôi thử nghiệm các phương pháp phân tích cú pháp của mình, nhưng nó không chỉ cho các phương pháp phân tích cú pháp. Nếu nó phức tạp hơn, tôi khuyên bạn nên xem Mox

+1

đẹp cách tiếp cận để thử nghiệm ngoại tuyến. Điều gì về chạy thử nghiệm ngoại tuyến để đảm bảo bạn không có lỗi mã và sau đó chạy thử nghiệm trực tuyến để đảm bảo rằng các thay đổi của trang web không làm hỏng chương trình của bạn? – Medeiros

+0

@Medeiros thats cách tôi đang làm điều đó trong một dự án khác ngay bây giờ. Tôi gắn thẻ các bài kiểm tra với @ integration = 1 để tôi không phải luôn chạy tất cả các bài kiểm tra. Tôi đang làm điều này với plugin gắn thẻ nosetests. –

+0

@SamStoelinga Tôi cũng có thể kiểm tra dữ liệu thực tế không? Nếu vậy làm thế nào tôi có thể "lấy" phản ứng bằng cách sử dụng mẩu tin lưu niệm bên trong bài kiểm tra đơn vị? Tôi rất muốn kiểm tra xem con nhện của tôi có tập hợp tất cả thông tin từ một mặt đã thay đổi hay không. – lony

1

Bạn có thể theo dõi this đoạn trích từ trang web cồng kềnh để chạy từ tập lệnh. Sau đó, bạn có thể thực hiện bất kỳ loại xác nhận bạn muốn trên các mặt hàng trả lại.

13

Giá trị mới được thêm vào là Spider Contracts đáng để thử. Nó cung cấp cho bạn một cách đơn giản để thêm các bài kiểm tra mà không đòi hỏi nhiều mã.

+4

Rất nghèo vào thời điểm hiện tại. Bạn phải viết các hợp đồng của riêng mình để kiểm tra điều gì đó phức tạp hơn * phân tích cú pháp của trang này trả về N mục với các trường 'foo' và' bar' chứa đầy bất kỳ dữ liệu nào * –

+0

Nó không phục vụ mục đích. Tôi đã thử thay đổi bộ chọn của mình và buộc các phản hồi trống vẫn được chuyển qua tất cả các hợp đồng –

9

tôi sử dụng Betamax để chạy thử nghiệm trên trang web thực sự là lần đầu tiên và tiếp tục phản ứng http tại địa phương để kiểm tra tiếp theo chạy sau khi siêu nhanh:

Betamax intercepts every request you make and attempts to find a matching request that has already been intercepted and recorded.

Khi bạn cần để có được phiên bản mới nhất của trang web, chỉ cần loại bỏ những gì betamax đã ghi lại và thử nghiệm lại.

Ví dụ:

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, tôi khám phá Betamax tại pycon 2015 nhờ Ian Cordasco's talk.

+0

Tôi đã sử dụng nó. Betamax là mát mẻ –

0

Tôi đang sử dụng Twisted's trial để chạy thử nghiệm, tương tự như các thử nghiệm của riêng Scrapy. Nó đã bắt đầu một lò phản ứng, vì vậy tôi sử dụng các CrawlerRunner mà không cần lo lắng về việc bắt đầu và dừng một trong các thử nghiệm.

Trộm cắp một số ý tưởng từ checkparse Scrapy lệnh tôi đã kết thúc với sau lớp cơ sở TestCase để chạy khẳng định chống lại các trang web trực tiếp:

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 

Ví dụ:

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

hoặc thực hiện một yêu cầu trong quá trình thiết lập và chạy nhiều thử nghiệm đối với kết quả:

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

Tôi đang sử dụng scrapy 1.3.0 và chức năng: fake_response_from_file, nâng cao một lỗi:

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

tôi nhận được:

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

Các giải pháp là sử dụng TextResponse thay vào đó, và nó hoạt động ok , ví dụ:

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

Cảm ơn rất nhiều.

+0

'response.encoding = 'utf-8'' phải được loại bỏ là tốt. –

0

Hơi đơn giản hơn, bằng cách loại bỏ các def fake_response_from_file từ câu trả lời lựa chọn:

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