为HTML字符串美化打印assertEqual()

5 投票
4 回答
3272 浏览
提问于 2025-04-17 05:37

我想在Python的单元测试中比较两个包含HTML的字符串。

有没有什么方法可以让结果以一种人类友好的方式输出,类似于差异对比的那种?

4 个回答

2

几年前我提交过一个补丁来解决这个问题。虽然这个补丁被拒绝了,但你仍然可以在Python的bug列表上查看它。

我怀疑你会想要修改你的unittest.py来应用这个补丁(而且经过这么长时间,它可能根本就不再有效),不过我可以给你一个函数,它可以把两个字符串缩减到一个更小的大小,同时保留一些不同的部分。只要你不想要完整的差异,这个可能就是你需要的:

def shortdiff(x,y):
    '''shortdiff(x,y)

    Compare strings x and y and display differences.
    If the strings are too long, shorten them to fit
    in one line, while still keeping at least some difference.
    '''
    import difflib
    LINELEN = 79
    def limit(s):
        if len(s) > LINELEN:
            return s[:LINELEN-3] + '...'
        return s

    def firstdiff(s, t):
        span = 1000
        for pos in range(0, max(len(s), len(t)), span):
            if s[pos:pos+span] != t[pos:pos+span]:
                for index in range(pos, pos+span):
                    if s[index:index+1] != t[index:index+1]:
                        return index

    left = LINELEN/4
    index = firstdiff(x, y)
    if index > left + 7:
        x = x[:left] + '...' + x[index-4:index+LINELEN]
        y = y[:left] + '...' + y[index-4:index+LINELEN]
    else:
        x, y = x[:LINELEN+1], y[:LINELEN+1]
        left = 0

    cruncher = difflib.SequenceMatcher(None)
    xtags = ytags = ""
    cruncher.set_seqs(x, y)
    editchars = { 'replace': ('^', '^'),
                  'delete': ('-', ''),
                  'insert': ('', '+'),
                  'equal': (' ',' ') }
    for tag, xi1, xi2, yj1, yj2 in cruncher.get_opcodes():
        lx, ly = xi2 - xi1, yj2 - yj1
        edits = editchars[tag]
        xtags += edits[0] * lx
        ytags += edits[1] * ly

    # Include ellipsis in edits line.
    if left:
        xtags = xtags[:left] + '...' + xtags[left+3:]
        ytags = ytags[:left] + '...' + ytags[left+3:]

    diffs = [ x, xtags, y, ytags ]
    if max([len(s) for s in diffs]) < LINELEN:
        return '\n'.join(diffs)

    diffs = [ limit(s) for s in diffs ]
    return '\n'.join(diffs)
3

一个简单的方法是去掉HTML中的空白字符,然后把它分割成一个列表。接着,使用Python 2.7的unittest(或者它的升级版unittest2)就可以得到一个人类可读的列表差异。

import re

def split_html(html):
    return re.split(r'\s*\n\s*', html.strip())

def test_render_html():
    expected = ['<div>', '...', '</div>']
    got = split_html(render_html())
    self.assertEqual(expected, got)

如果我在为已经能正常工作的代码写测试,我通常会先设置expected = [],然后在断言之前插入self.maxDiff = None,让测试先失败一次。这样,期望的列表就可以从测试输出中复制粘贴过来了。

你可能需要根据你的HTML的具体情况调整去掉空白字符的方法。

1

我(提问的人)现在在使用BeautifulSoup:

def assertEqualHTML(string1, string2, file1='', file2=''):
    u'''
    Compare two unicode strings containing HTML.
    A human friendly diff goes to logging.error() if there
    are not equal, and an exception gets raised.
    '''
    from BeautifulSoup import BeautifulSoup as bs
    import difflib
    def short(mystr):
        max=20
        if len(mystr)>max:
            return mystr[:max]
        return mystr
    p=[]
    for mystr, file in [(string1, file1), (string2, file2)]:
        if not isinstance(mystr, unicode):
            raise Exception(u'string ist not unicode: %r %s' % (short(mystr), file))
        soup=bs(mystr)
        pretty=soup.prettify()
        p.append(pretty)
    if p[0]!=p[1]:
        for line in difflib.unified_diff(p[0].splitlines(), p[1].splitlines(), fromfile=file1, tofile=file2):
            logging.error(line)
        raise Exception('Not equal %s %s' % (file1, file2))

撰写回答