Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 55 additions & 5 deletions Lib/test/test_unittest/test_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
from itertools import product


NAN = float('nan')
INF = float('inf')
NINF = float('-inf')


class Test_Assertions(unittest.TestCase):
def test_AlmostEqual(self):
self.assertAlmostEqual(1.00000001, 1.0)
Expand All @@ -26,11 +31,7 @@ def test_AlmostEqual(self):
self.assertRaises(self.failureException,
self.assertNotAlmostEqual, 0, .1+.1j, places=0)

self.assertAlmostEqual(float('inf'), float('inf'))
self.assertRaises(self.failureException, self.assertNotAlmostEqual,
float('inf'), float('inf'))

def test_AmostEqualWithDelta(self):
def test_AlmostEqualWithDelta(self):
self.assertAlmostEqual(1.1, 1.0, delta=0.5)
self.assertAlmostEqual(1.0, 1.1, delta=0.5)
self.assertNotAlmostEqual(1.1, 1.0, delta=0.05)
Expand All @@ -57,6 +58,55 @@ def test_AmostEqualWithDelta(self):
self.assertNotAlmostEqual(first, second,
delta=datetime.timedelta(seconds=5))

def test_AlmostEqualWithRelativeDelta(self):
self.assertAlmostEqual(1.0, 0.6, rel_delta=0.5)
self.assertAlmostEqual(0.6, 1.0, rel_delta=0.5)
with self.assertRaises(self.failureException):
self.assertAlmostEqual(1.0, 0.6, rel_delta=0.05)
with self.assertRaises(self.failureException):
self.assertAlmostEqual(0.6, 1.0, rel_delta=0.05)

self.assertNotAlmostEqual(1.0, 0.6, rel_delta=0.05)
self.assertNotAlmostEqual(0.6, 1.0, rel_delta=0.05)
with self.assertRaises(self.failureException):
self.assertNotAlmostEqual(1.0, 0.6, rel_delta=0.5)
with self.assertRaises(self.failureException):
self.assertNotAlmostEqual(0.6, 1.0, rel_delta=0.5)

with self.assertRaises(TypeError):
self.assertAlmostEqual(1.0, 1.1, places=0, rel_delta=0.5)
with self.assertRaises(TypeError):
self.assertAlmostEqual(1.0, 1.1, delta=1, rel_delta=0.5)
with self.assertRaises(TypeError):
self.assertNotAlmostEqual(1.0, 1.1, places=0, rel_delta=0.05)
with self.assertRaises(TypeError):
self.assertNotAlmostEqual(1.0, 1.1, delta=0.05, rel_delta=0.05)

first_dt = datetime.timedelta(seconds=20)
second_dt = datetime.timedelta(seconds=15)

self.assertAlmostEqual(first_dt, second_dt, rel_delta=0.5)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add two datetime objects together with timedelta as rel_delta

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my point of view this seems not well defined. The clearest method to see are the units: Within the calculation, we multiply the relative delta with one of the objects: timedelta * datetime would have a unit of square seconds ...

self.assertNotAlmostEqual(first_dt, second_dt, rel_delta=0.1)

def test_AlmostEqual_inf_nan(self):
for criterion in [{"places": 2}, {"delta": 0.5}, {"rel_delta": 0.2}]:
for n1, n2 in [(INF, INF), (NINF, NINF)]:
self.assertAlmostEqual(n1, n2, **criterion)
with self.assertRaises(self.failureException,
msg="%s %s should be equal with %s" % (n1, n2, criterion)):
self.assertNotAlmostEqual(n1, n2, **criterion)

for n1, n2 in [(INF, NINF), (NINF, INF),
(INF, 1), (1, INF),
(NINF, 1), (1, NINF),
(NAN, NAN),
(NAN, 1), (1, NAN),
(NAN, INF), (INF, NAN),
(NAN, NINF), (NINF, NAN)]:
self.assertNotAlmostEqual(n1, n2, **criterion)
with self.assertRaises(self.failureException):
self.assertAlmostEqual(n1, n2, **criterion)

def test_assertRaises(self):
def _raise(e):
raise e
Expand Down
68 changes: 48 additions & 20 deletions Lib/unittest/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -958,24 +958,33 @@ def assertNotEqual(self, first, second, msg=None):
raise self.failureException(msg)

def assertAlmostEqual(self, first, second, places=None, msg=None,
delta=None):
delta=None, rel_delta=None):
"""Fail if the two objects are unequal as determined by their
difference rounded to the given number of decimal places
(default 7) and comparing to zero, or by comparing that the
difference between the two objects is more than the given
delta.
difference by one of the following methods:

Note that decimal places (from zero) are usually not the same
as significant digits (measured from the most significant digit).
1) Given number of decimal places. This is the default method
(default value 7 digits). Note that decimal places (from
zero) are usually not the same as significant digits
(measured from the most significant digit).

2) Comparing that the absolute difference between the two
objects is more than the given delta.

3) Comparing that the relative difference between the two
objects is more than the given rel_delta, using the same
algorithm as :meth:`math.isclose`.

If the two objects compare equal then they will automatically
compare almost equal.
compare almost equal. Comparison of floating point numbers
(espaicially inf, -inf, and NaN) is applied in the same way as
in :meth:`math.isclose` according to IEEE standard 754.
"""
if first == second:
# shortcut
return
if delta is not None and places is not None:
raise TypeError("specify delta or places not both")
if int(delta is not None) + int(places is not None) + \
int(rel_delta is not None) > 1:
raise TypeError("specify maximally one: delta, places or rel_delta")

diff = abs(first - second)
if delta is not None:
Expand All @@ -987,6 +996,15 @@ def assertAlmostEqual(self, first, second, places=None, msg=None,
safe_repr(second),
safe_repr(delta),
safe_repr(diff))
elif rel_delta is not None:
if (diff < rel_delta*abs(first)) or (diff < rel_delta*abs(second)):
return

standardMsg = '%s != %s within relatively %s (%s rel. difference)' % (
safe_repr(first),
safe_repr(second),
safe_repr(rel_delta),
safe_repr(diff/abs(first)))
else:
if places is None:
places = 7
Expand All @@ -1003,28 +1021,38 @@ def assertAlmostEqual(self, first, second, places=None, msg=None,
raise self.failureException(msg)

def assertNotAlmostEqual(self, first, second, places=None, msg=None,
delta=None):
delta=None, rel_delta=None):
"""Fail if the two objects are equal as determined by their
difference rounded to the given number of decimal places
(default 7) and comparing to zero, or by comparing that the
difference between the two objects is less than the given delta.

Note that decimal places (from zero) are usually not the same
as significant digits (measured from the most significant digit).
difference by one of the methods analougsly described for
assertAlmostEqual().

Objects that are equal automatically fail.
"""
if delta is not None and places is not None:
raise TypeError("specify delta or places not both")
if int(delta is not None) + int(places is not None) + \
int(rel_delta is not None) > 1:
raise TypeError("specify maximally one: delta, places or rel_delta")

diff = abs(first - second)

if delta is not None:
if not (first == second) and diff > delta:
if not (first == second) and ((diff > delta) or diff != diff):
return
standardMsg = '%s == %s within %s delta (%s difference)' % (
safe_repr(first),
safe_repr(second),
safe_repr(delta),
safe_repr(diff))
elif rel_delta is not None:
if not (first == second) and \
(((diff > rel_delta*abs(first)) and (diff > rel_delta*abs(second)))
or (diff != diff) or (diff/2 == diff)):
return

standardMsg = '%s == %s within relatively %s (%s rel. difference)' % (
safe_repr(first),
safe_repr(second),
safe_repr(rel_delta),
safe_repr(diff/abs(first)))
else:
if places is None:
places = 7
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A new parameter ``rel_delta`` was added to the :meth:`unittest.TestCase.assertAlmostEqual` and :meth:`unittest.TestCase.assertNotAlmostEqual`. It allows comparing two objects with a maximum relative difference to each other.
Loading