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

Python 在django中,如何从我的站点上的已知URL列表开始,在每个视图中递归地单元测试所有链接(检查200OK),python,django,unit-testing,testing,pytest,Python,Django,Unit Testing,Testing,Pytest,在django应用程序中,如果不为每个视图显式编写单元测试,如何测试是否没有断开的链接 测试应该“浏览”到它在站点上找到的所有链接,并测试“200 OK”响应。以下代码对我非常有用。 它立即显示了一些断开的链接,所以我想在这里分享 它从URL列表开始递归地遍历所有链接,并检查200 OK响应 还可以给出要避免的URL列表 注: 使用django 1.8.16和python 2.7进行测试 需要beatifulsoup4(pip安装beatifulsoup4) 我们开始: from __fut

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


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

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

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

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

注:

  • 使用django 1.8.16和python 2.7进行测试
  • 需要beatifulsoup4(pip安装beatifulsoup4)
我们开始:

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 REACHED\nStats:')
        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

pip安装pythonlinkvalidator


pylinkvalidate.py-phttp://www.yourdjangosite.com/

这个问题的解决方案看起来太复杂了!确实如此,但是会处理带有参数的URL和数据库中的实际(测试)数据。您也打算检查外部URL吗?听起来我不是个好主意;非站点资源关闭不应导致测试失败……不,外部URL被排除在“避免列表”中。这确实使单元测试独立于外部行为而工作。谢谢你的提示。但它的目的是在一个持续集成环境中运行,而不是在生产服务器上运行。很好,很短,但是它不能正确地遍历带有参数的URL,对吗?是的,它不会。也许你应该自定义一段代码,检查url所需的数量、类型或参数,并传递一些真实/虚拟数据。我考虑过用arguemnts验证url,但这似乎没有简单的解决方案,因为在这种情况下有很多因素。让我有一个带有模式
r'^blog/(?P[^\.]+)$
)的url,我必须确保slug是简单的字符串或数字或某种模式(这里我不使用模式),甚至我可以基于模式创建文本,新的
slug
应该存在于数据库中,以便知道,我需要知道博客的
模式。因此,您可能必须为ONCE创建自己的解决方案,并使用参数或硬代码。请参阅下面我提出的解决方案。更复杂,我同意,但对我来说很有用。