Имеет ли pytest эквивалент assertItemsEqual/assertCountEqual

unittest.TestCase имеет assertCountEqual метод (assertItemsEqual в Python 2, что, возможно, лучшее имя), в котором сравниваются два итераторы и проверки, что они содержат одинаковое количество одинаковых объектов, независимо от их порядка.

Обеспечивает ли pytest нечто подобное? Все очевидные альтернативы (например, вызов set(x), sorted(x) или Counter(list(x)) с каждой стороны, как указано в документации) не работают, потому что вещи, которые я сравниваю, это списки словарей, а словари не являются hashable.

Ответ 1

pytest не предоставляет assertCountEqual, но мы можем использовать unittest:

import unittest

def test_stuff():
    case = unittest.TestCase()
    a = [{'a': 1}, {'b': 2}]
    b = [{'b': 2}]
    case.assertCountEqual(a, b)

И результат приличный, тоже

$ py.test
============================= test session starts ==============================
platform linux -- Python 3.6.2, pytest-3.2.1, py-1.4.34, pluggy-0.4.0
rootdir: /home/they4kman/.virtualenvs/tmp-6626234b42fb350/src, inifile:
collected 1 item

test_stuff.py F

=================================== FAILURES ===================================
__________________________________ test_stuff __________________________________

    def test_stuff():
        case = unittest.TestCase()
        a = [{'a': 1}, {'b': 2}]
        b = [{'b': 2}]
>       case.assertCountEqual(a, b)

test_stuff.py:7:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/lib/python3.6/unittest/case.py:1182: in assertCountEqual
    self.fail(msg)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <unittest.case.TestCase testMethod=runTest>
msg = "Element counts were not equal:\nFirst has 1, Second has 0:  {'a': 1}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Element counts were not equal:
E       First has 1, Second has 0:  {'a': 1}

/usr/lib/python3.6/unittest/case.py:670: AssertionError
=========================== 1 failed in 0.07 seconds ==========================

Примечание: реализация assertCountEqual включает ветвь специально для нераспаковываемых типов, которая выполняет кучу учета и сравнивает каждый элемент с любым другим элементом.

Ответ 2

Вы можете создать универсальную вспомогательную функцию, которая работает в большинстве случаев:

def items_equal(xs, ys):
    if isinstance(xs, dict) and isinstance(ys, dict):
        if len(xs) != len(ys):
            return False
        for key in xs.keys():
            try:
                if not items_equal(xs[key], ys[key]):
                    return False
            except KeyError:
                return False
        return True
    elif isinstance(xs, list) and isinstance(ys, list):
        if len(xs) != len(ys):
            return False
        sxs = xs
        sys = ys
        try:
            sxs = sorted(xs)
            sys = sorted(ys)
            for x, y in zip(sxs, sys):
                if not items_equal(x, y):
                    return False
        except TypeError:
            ys_copy = ys.copy()
            for x in xs:
                matches = [i for i, y in enumerate(ys_copy) if items_equal(x, y)]
                if len(matches):
                    del ys_copy[matches[0]]
                    continue
                else:
                    return False
        return True
    else:
        return xs == ys