I am writing a Python2 module that emulates a certain library. The results may be float
, int
, long
, unicode
, str
, tuple
, list
, and custom objects. Lists may not contain lists, but they may contain tuples. Tuples may not contain lists or tuples. Otherwise, lists and tuples may contain any of the other types listed above.
(Actually, the module should not return long
or str
, but if it does, they should be caught and reported as different when compared to int
and unicode
, respectively.)
I am writing a testing program that checks the results against known answers by the library my module tries to emulate. The obvious answer would be to test the values and the types, but one problem I'm facing is that in corner cases, possible results to test for are -0.0
(which should be distinguished from 0.0
) and NaN
(Not a Number - a value a float can take).
However:
>>> a = float('nan')
>>> b = float('nan')
>>> a == b
False
>>> c = float('-0.0')
>>> c
-0.0
>>> d = 1.0 - 1.0
>>> c == d
True
The is
operator doesn't help a bit:
>>> a is b
False
>>> d is 0.0
False
repr
helps:
>>> repr(a) == repr(b)
True
>>> repr(c) == repr(d)
False
>>> repr(d) == repr(0.0)
True
But only to a point, since it doesn't help with objects:
>>> class e:
... pass
...
>>> f = e()
>>> g = e()
>>> f.x = float('nan')
>>> g.x = float('nan')
>>> f == g
False
>>> repr(f) == repr(g)
False
This works though:
>>> repr(f.__dict__) == repr(g.__dict__)
True
But it fails with tuples and lists:
>>> h = [float('nan'), f]
>>> i = [float('nan'), g]
>>> h == i
False
>>> repr(h) == repr(i)
False
>>> repr(h.__dict__) == repr(i.__dict__)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__dict__'
It seems I'm close, so I need to know:
- Is there a simpler way to check for actual equality that doesn't have the burden of converting to string?
- If not, how would I go about comparing lists or tuples containing objects?
Edit: To be clear, what I'm after is a full comparison function. My test function looks roughly like this:
>>> def test(expression, expected):
... actual = eval(expression)
... if not reallyequal(actual, expected):
... report_error(expression, actual, expected)
My question concerns what should reallyequal() look like.
Edit 2: I've found the Python standard module unittest but unfortunately none of the checks covers this use case, so it seems that if I intend to use it, I should use something like self.assertTrue(reallyequal(actual, expected))
.
I'm actually surprised that it's so hard to make unit tests including expected NaNs and minus zeros nested within the results. I'm still using the repr solution which is a half-solution, but I'm open to other ideas.