【问题标题】:Assert that two dictionaries are almost equal断言两个字典几乎相等
【发布时间】:2014-06-26 07:07:25
【问题描述】:

我试图断言两个字典几乎相等,但我似乎做不到。

这是一个例子:

>>> import nose.tools as nt
>>> nt.assert_dict_equal({'a' : 12.4}, {'a' : 5.6 + 6.8})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/unittest/case.py", line 838, in assertDictEqual
    self.fail(self._formatMessage(msg, standardMsg))
  File "/usr/lib/python2.7/unittest/case.py", line 413, in fail
    raise self.failureException(msg)
AssertionError: {'a': 12.4} != {'a': 12.399999999999999}
- {'a': 12.4}
+ {'a': 12.399999999999999}

我希望它通过,像这样:

>>> nt.assert_almost_equal(12.4, 5.6 + 6.8)

我希望我遗漏了一些简单的东西,比如nt.assert_almost_dict_equal,或者我可以传递给nt.assert_dict_equal 的参数来指定浮点应该有多接近,但我找不到任何东西。

当然,我可以遍历字典并使用nt.assert_almost_equal 单独比较值;但是,就我而言,字典更复杂,所以我希望避免这种情况。

断言两个字典几乎相等的最佳方法是什么?

【问题讨论】:

  • 我认为您需要自己迭代和比较这些值。 assert_almost_equal 只提供给可以直接计算差值的数值类型。
  • 如果您发现自己需要滚动,请在此处查看“assertDeepAlmostEqual”:github.com/larsbutler/oq-engine/blob/master/tests/utils/…
  • @dano,这很有趣,谢谢。

标签: python dictionary assert nose


【解决方案1】:

@dano 的评论回答了我的问题:

我从link provided by dano复制了一个函数

import unittest
import numpy

def assertDeepAlmostEqual(test_case, expected, actual, *args, **kwargs):
    """
    Assert that two complex structures have almost equal contents.

    Compares lists, dicts and tuples recursively. Checks numeric values
    using test_case's :py:meth:`unittest.TestCase.assertAlmostEqual` and
    checks all other values with :py:meth:`unittest.TestCase.assertEqual`.
    Accepts additional positional and keyword arguments and pass those
    intact to assertAlmostEqual() (that's how you specify comparison
    precision).

    :param test_case: TestCase object on which we can call all of the basic
    'assert' methods.
    :type test_case: :py:class:`unittest.TestCase` object
    """
    is_root = not '__trace' in kwargs
    trace = kwargs.pop('__trace', 'ROOT')
    try:
        if isinstance(expected, (int, float, long, complex)):
            test_case.assertAlmostEqual(expected, actual, *args, **kwargs)
        elif isinstance(expected, (list, tuple, numpy.ndarray)):
            test_case.assertEqual(len(expected), len(actual))
            for index in xrange(len(expected)):
                v1, v2 = expected[index], actual[index]
                assertDeepAlmostEqual(test_case, v1, v2,
                                      __trace=repr(index), *args, **kwargs)
        elif isinstance(expected, dict):
            test_case.assertEqual(set(expected), set(actual))
            for key in expected:
                assertDeepAlmostEqual(test_case, expected[key], actual[key],
                                      __trace=repr(key), *args, **kwargs)
        else:
            test_case.assertEqual(expected, actual)
    except AssertionError as exc:
        exc.__dict__.setdefault('traces', []).append(trace)
        if is_root:
            trace = ' -> '.join(reversed(exc.traces))
            exc = AssertionError("%s\nTRACE: %s" % (exc.message, trace))
        raise exc

# My part, using the function

class TestMyClass(unittest.TestCase):
    def test_dicts(self):
        assertDeepAlmostEqual(self, {'a' : 12.4}, {'a' : 5.6 + 6.8})
    def test_dicts_2(self):
        dict_1 = {'a' : {'b' : [12.4, 0.3]}}
        dict_2 = {'a' : {'b' : [5.6 + 6.8, 0.1 + 0.2]}}

        assertDeepAlmostEqual(self, dict_1, dict_2)

def main():
    unittest.main()

if __name__ == "__main__":
    main()

结果:

Ran 2 tests in 0.000s

OK

【讨论】:

  • 投反对票的人,你能解释一下投反对票的原因吗?我相信自我回答很好。你认为我对@dano 的评价不够吗?
  • 这可能是一种不好的风格,但如果你通过'unittest.TestCase.assertDeepAlmostEqual = assertDeepAlmostEqual`对TestCase进行猴子补丁,那么你可以像使用其他任何测试一样使用测试,例如self.assertDeepAlmostEqual(dict_1, dict_2)
  • 还应注意,在我的情况下,我必须对所有嵌套集合进行排序,因此您可以将此处的代码与此代码 sn-p 一起用于ordering nested collection
  • 无需猴子补丁TestCase。您可以简单地对其进行子类化。事实上,testtools.TestCase 就是这样做的。
  • Python 2 不再受支持,因此我将 xrange 替换为 rangelong 也应该是 numpy.long 或者您可以明确地 from numpy import long, ndarray 并保存导入所有 numpy。
【解决方案2】:

我知道你不会仅仅为了这样做而导入 pandas,但如果你碰巧使用 pandas,你可以将 dicts 转换为系列并使用来自pandas.testingassert_series_equal,默认情况下,check_exact=False

>>> import pandas as pd
>>> from pandas.testing import assert_series_equal
>>> a = pd.Series({'a' : 12.4})
>>> b = pd.Series({'a': 12.399999999999999})
>>> assert_series_equal(a, b)

【讨论】:

    【解决方案3】:

    Pytest “大约”完成这项工作

    In [10]: {'a': 2.000001} == pytest.approx({'a': 2}) Out[10]: True

    【讨论】:

      【解决方案4】:

      我无法让 Akavall 的函数运行,所以我自己做了。有点太简单了,但适用于我的目的。测试该函数的代码是使用 pytest 编写的

      from numbers import Number
      from math import isclose
      
      def dictsAlmostEqual(dict1, dict2, rel_tol=1e-8):
          """
          If dictionary value is a number, then check that the numbers are almost equal, otherwise check if values are exactly equal
          Note: does not currently try converting strings to digits and comparing them. Does not care about ordering of keys in dictionaries
          Just returns true or false
          """
          if len(dict1) != len(dict2):
              return False
          # Loop through each item in the first dict and compare it to the second dict
          for key, item in dict1.items():
              # If it is a nested dictionary, need to call the function again
              if isinstance(item, dict):
                  # If the nested dictionaries are not almost equal, return False
                  if not dictsAlmostEqual(dict1[key], dict2[key], rel_tol=rel_tol):
                      return False
              # If it's not a dictionary, then continue comparing
              # Put in else statement or else the nested dictionary will get compared twice and
              # On the second time will check for exactly equal and will fail
              else:
                  # If the value is a number, check if they are approximately equal
                  if isinstance(item, Number):
                      # if not abs(dict1[key] - dict2[key]) <= rel_tol:
                      # https://stackoverflow.com/questions/5595425/what-is-the-best-way-to-compare-floats-for-almost-equality-in-python
                      if not isclose(dict1[key], dict2[key], rel_tol=rel_tol):
                          return False
                  else:
                      if not (dict1[key] == dict2[key]):
                          return False
          return True
      

      使用 pytest 验证函数输出

      import pytest
      import dictsAlmostEqual
      def test_dictsAlmostEqual():
          a = {}
          b = {}
          assert dictsAlmostEqual(a, b)
          a = {"1": "a"}
          b = {}
          assert not dictsAlmostEqual(a, b)
          a = {"1": "a"}
          b = {"1": "a"}
          assert dictsAlmostEqual(a, b)
          a = {"1": "a"}
          b = {"1": "b"}
          assert not dictsAlmostEqual(a, b)
          a = {"1": "1.23"}
          b = {"1": "1.23"}
          assert dictsAlmostEqual(a, b)
          a = {"1": "1.234"}
          b = {"1": "1.23"}
          assert not dictsAlmostEqual(a, b)
          a = {"1": 1.000000000000001, "2": "a"}
          b = {"1": 1.000000000000002, "2": "a"}
          assert not dictsAlmostEqual(a, b, rel_tol=1e-20)
          assert dictsAlmostEqual(a, b, rel_tol=1e-8)
          assert dictsAlmostEqual(a, b)
          # Nested dicts
          a = {"1": {"2": 1.000000000000001}}
          b = {"1": {"2": 1.000000000000002}}
          assert not dictsAlmostEqual(a, b, rel_tol=1e-20)
          assert dictsAlmostEqual(a, b, rel_tol=1e-8)
          assert dictsAlmostEqual(a, b)
          a = {"1": {"2": 1.000000000000001, "3": "a"}, "2": "1.23"}
          b = {"1": {"2": 1.000000000000002, "3": "a"}, "2": "1.23"}
          assert not dictsAlmostEqual(a, b, rel_tol=1e-20)
          assert dictsAlmostEqual(a, b, rel_tol=1e-8)
          assert dictsAlmostEqual(a, b)
      

      【讨论】:

        猜你喜欢
        • 2012-01-23
        • 1970-01-01
        • 1970-01-01
        • 2020-11-27
        • 2020-01-08
        • 1970-01-01
        • 1970-01-01
        • 2010-09-29
        • 2017-11-07
        相关资源
        最近更新 更多