Python unittest failure when results appear equal

2019-07-08 05:59发布

I'm running a series of unit tests on an RPN calculator I've just put together. This calculator can mix integers, floats, and picas. I often need to be able to calculate using picas for some typesetting work I'm doing.

For some reason, one of my unit tests is failing:

Failure
Traceback (most recent call last):
  File "/Users/andrew/Developer/pyRpn/test_rpn.py", line 112, in test_pica_subtracted_from_pica
    self.assertEqual(self.calc.rpn(['10p3', '2p1', '-']), ['8p2'])
AssertionError: Lists differ: ['8p2'] != ['8p2']

First differing element 0:
'8p2'
8p2

  ['8p2']

I can't see why. To simplify I took the list out of the equation and compared directly to a string:

Failure
Expected :'8p2'
Actual   :'8p2'
 <Click to see difference>

Traceback (most recent call last):
  File "/Users/andrew/Developer/pyRpn/test_rpn.py", line 112, in test_pica_subtracted_from_pica
    self.assertEqual(self.calc.rpn(['10p3', '2p1', '-']).pop(), '8p2')
AssertionError: '8p2' != '8p2'

It still fails. I cannot see why '8p2' != '8p2'. Running in PyCharm if I click to see the difference, it tells me there are no differences, and the content is identical, yet the test fails. It fails from the command line as well.

I've put the same test in as a doctest:

"""
>>> RPN().rpn(['10p3', '2p1', '-'])
['8p2']
"""

and it passes without a problem.


MCVE:

import re
import unittest


class Pica(object):
    def __init__(self, points):
        self.points = points

    def __repr__(self):
        whole_points = int(self.points / 12)
        sub_points = self.points - (whole_points * 12)
        return "'%rp%r'" % (whole_points, sub_points)

    def __sub__(self, other):
        if type(other) is Pica:
            return Pica(self.points - other.points)


class RPN:
    def __init__(self):
        self.oper_dict = {'-': RPN.pop_two_and_sub}
        self.pica_pattern = re.compile("(\d+)p(\d+)")

    def pop_two_and_sub(self, terms):
        terms.append(terms.pop() - terms.pop())

    def rpn(self, terms):
        result = []
        for term in terms:
            if term in self.oper_dict:
                self.oper_dict[term](self, result)
            elif self.pica_pattern.match(term):
                match = self.pica_pattern.match(term)
                result.append(Pica(int(match.group(1)) * 12 + int(match.group(2))))
            else:
                raise SyntaxError
        return result


class TestPica(unittest.TestCase):
    def test_pica_subtracted_from_pica(self):
        self.assertCountEqual(RPN().rpn(['2p1', '10p3', '-']), ['8p2'])


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

When I run this with Python 3.5, I get the following error:

Failure
Traceback (most recent call last):
  File "/Users/andrew/Developer/pyRpn/mvce.py", line 42, in test_pica_subtracted_from_pica
    self.assertCountEqual(RPN().rpn(['2p1', '10p3', '-']), ['8p2'])
AssertionError: Element counts were not equal:
First has 1, Second has 0:  '8p2'
First has 0, Second has 1:  '8p2'

2条回答
甜甜的少女心
2楼-- · 2019-07-08 06:21

The results appear equal, however they are not. You are not comparing a list of strings. rpn() can return a list that holds Pica objects:

>>> from rpn import RPN
>>> type(RPN().rpn(['2p1', '10p3', '-'])[0])
<class 'rpn.Pica'>

This shows that the first element of the list returned by rpn() is a Pica. So, comparing a Pica to a string is going to return False, thus your assertion fails.

You can add a __eq__() method to your Pica class, then you can compare Picas with Picas:

def __eq__(self, other):
    """Test two Pica objects for equality"""
    return self.points == other.points

Then you could do this in your test:

self.assertEqual(self.calc.rpn(['10p3', 2, '-']), [Pica(8*12+3)])

It would be nice to also be able to create a Pica from a string:

def __init__(self, value):
    if isinstance(value, str):
        a,b = value.split('p')
        value = int(a) * 12 + int(b)
    self.points = value

Now you can test like this:

self.assertEqual(self.calc.rpn(['10p3', 2, '-']), [Pica('8p3')])
查看更多
何必那么认真
3楼-- · 2019-07-08 06:40

Ok, I found the difference. Python 3 has more support for unicode, but it doesn't show the encoding that is in effect when printing a unicode string, which is why they printed the same even though they had different types:

  1. <class '__main__.Pica'>
  2. <class 'str'>

In order for the assertion to work, either a smarter compare is needed, or else the strings need to be put in a common format before calling the assert methods.

查看更多
登录 后发表回答