在 django 中,我如何从我网站上的已知 URL 列表开始,递归地测试每个视图的所有链接(检查 200 OK)



在 django 应用程序中,如何在不为每个视图显式编写单元测试的情况下测试是否没有断开的链接?

测试应该"浏览"到它在网站上找到的所有链接,并测试"200 OK"响应。

以下一段代码被证明对我非常有用。它立即揭示了一些断开的链接,所以我想在这里分享。

它从 URL 列表开始递归遍历所有链接,并检查 200 OK 响应。

还可以给出要避免的 URL 列表。

笔记:

  • 使用 django 1.8.16 和 python 2.7 进行测试
  • 需要美汤4
  • (点安装美汤4(

来吧:

from __future__ import print_function
from django.test import TestCase
from bs4 import BeautifulSoup
import re
from django.contrib.auth.models import User
VERBOSE = True

class TraverseLinksTest(TestCase):
    def setUp(self):
        # By default, login as superuser
        self.superuser = User.objects.create_superuser('superuser1', 'superuser1@example.com', 'pwd')
        if self.client.login(username="superuser1@example.com", password="pwd"):
            if VERBOSE: print('nLogin as superuser OK')
        else:
            raise BaseException('Login failed')
    @classmethod
    def setUpTestData(cls):
        # Initialise your database here as needed
        pass
    def test_traverse_urls(self):
        # Fill these lists as needed with your site specific URLs to check and to avoid
        to_traverse_list = ['/mysite', '/mysite/sub-page']
        to_avoid_list = ['^/$', '^$', 'javascript:history.back()', 'javascript:history.go(-1)', '^mailto:.*', '.*github.io.*']
        done_list = []
        error_list = []
        source_of_link = dict()
        for link in to_traverse_list:
            source_of_link[link] = 'initial'
        (to_traverse_list, to_avoid_list, done_list, error_list, source_of_link) = 
            self.recurse_into_path(to_traverse_list, to_avoid_list, done_list, error_list, source_of_link)
        print('END REACHEDnStats:')
        if VERBOSE: print('nto_traverse_list = ' + str(to_traverse_list))
        if VERBOSE: print('nto_avoid_list = ' + str(to_avoid_list))
        if VERBOSE: print('nsource_of_link = ' + str(source_of_link))
        if VERBOSE: print('ndone_list = ' + str(done_list))
        print('Followed ' + str(len(done_list)) + ' links successfully')
        print('Avoided ' + str(len(to_avoid_list)) + ' links')
        if error_list:
            print('!! ' + str(len(error_list)) + ' error(s) : ')
            for error in error_list:
                print(str(error) + ' found in page ' + source_of_link[error[0]])
            print('Errors found traversing links')
            assert False
        else:
            print('No errors')
    def recurse_into_path(self, to_traverse_list, to_avoid_list, done_list, error_list, source_of_link):
        """ Dives into first item of to_traverse_list
            Returns: (to_traverse_list, to_avoid_list, done_list, source_of_link)
        """
        if to_traverse_list:
            url = to_traverse_list.pop()
            if not match_any(url, to_avoid_list):
                print('Surfing to ' + str(url) + ', discovered in ' + str(source_of_link[url]))
                response = self.client.get(url, follow=True)
                if response.status_code == 200:
                    soup = BeautifulSoup(response.content, 'html.parser')
                    text = soup.get_text()
                    for link in soup.find_all('a'):
                        new_link = link.get('href')
                        if VERBOSE: print('  Found link: ' + str(new_link))
                        if match_any(new_link, to_avoid_list):
                            if VERBOSE: print('    Avoiding it')
                        elif new_link in done_list:
                            if VERBOSE: print('    Already done, ignoring')
                        elif new_link in to_traverse_list:
                            if VERBOSE: print('    Already in to traverse list, ignoring')
                        else:
                            if VERBOSE: print('    New, unknown link: Storing it to traverse later')
                            source_of_link[new_link] = url
                            to_traverse_list.append(new_link)
                    done_list.append(url)
                    if VERBOSE: print('Done')
                else:
                    error_list.append((url, response.status_code))
                    to_avoid_list.append(url)
            if VERBOSE: print('Diving into next level')
            return self.recurse_into_path(to_traverse_list, to_avoid_list, done_list, error_list, source_of_link)
        else:
            # Nothing to traverse
            if VERBOSE: print('Returning to upper level')
            return to_traverse_list, to_avoid_list, done_list, error_list, source_of_link

def match_any(my_string, regexp_list):
    if my_string:
        combined = "(" + ")|(".join(regexp_list) + ")"
        return re.match(combined, my_string)
    else:
        # 'None' as string always matches
        return True

尝试这样的事情:

from django.core.urlresolvers import reverse
from django.test import TestCase
from django.core.urlresolvers import get_resolver

class WebPagesTests(TestCase):
    def test_static_pages(self):
        urls = get_resolver(None).reverse_dict.keys()
        #urls = ['get_started', 'website_about', 'website_hiring',
        #            'terms_of_service', 'privacy_policy']
        for url in urls:
            url = reverse(url)
            resp = self.client.get(url)
            self.assertEqual(resp.status_code, 200)

如果你的网站是在线的,你可以使用一个标准的链接检查器,python有几个。

我发现在python 3中易于使用和安装的那个是pythonlinkvalidator - https://github.com/bartdag/pylinkvalidator

pip install pythonlinkvalidator

pylinkvalidate.py -P http://www.yourdjangosite.com/

最新更新