92

I'm trying to make some unit tests with pytest.

I was thinking about doing things like that:

actual = b_manager.get_b(complete_set)
assert actual is not None
assert actual.columns == ['bl', 'direction', 'day']

The first assertion in ok but with the second I have an value error.

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

I assume it is not the right way to assert the equality of two different lists with pytest.

How can I assert that the dataframe columns (a list) is equal to the expected one?

Thanks

bAN
  • 13,375
  • 16
  • 60
  • 93
  • 2
    The traceback includes a hint... Use `a.any()` or `a.all()`. BTW `assert` is not the 'normal' way to do unittesting – Chris_Rands Oct 24 '17 at 15:28
  • 29
    @Chris_Rands `assert` is **THE** way to test values under `pytest`. `pytest` internally rewrites byte code of `assert`s and calls its own comparison function. – phd Oct 24 '17 at 16:25
  • Is `actual.columns` a list? The traceback suggests it's a bool. – phd Oct 24 '17 at 16:29
  • actual.columns is a list indeed. After a little bit of investigations I realized that the comparison returns another list with booleans to check if the content is different or not. [True, False, True, True ..].And that's why I have to use the .All().. To be able to give to the Assert a unique Boolean not a list of booleans – bAN Oct 25 '17 at 08:46
  • @Chris_Rands I'm using pytest, not the builtin unittest framework. Then what should be a normal way to do unittest? – bAN Oct 25 '17 at 08:48
  • I think this is best answered in this older question https://stackoverflow.com/a/45946306/11715259 – N1ngu Feb 03 '22 at 11:48

7 Answers7

109

See this:

Note:

You can simply use the assert statement for asserting test expectations. pytest’s Advanced assertion introspection will intelligently report intermediate values of the assert expression freeing you from the need to learn the many names of JUnit legacy methods.

And this:

Special comparisons are done for a number of cases:

  • comparing long strings: a context diff is shown
  • comparing long sequences: first failing indices
  • comparing dicts: different entries

And the reporting demo:

failure_demo.py:59: AssertionError
_______ TestSpecialisedExplanations.test_eq_list ________

self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>

    def test_eq_list(self):
>       assert [0, 1, 2] == [0, 1, 3]
E       assert [0, 1, 2] == [0, 1, 3]
E         At index 2 diff: 2 != 3
E         Use -v to get the full diff

See the assertion for lists equality with literal == over there? pytest has done the hard work for you.

Rockallite
  • 16,437
  • 7
  • 54
  • 48
  • 5
    Good answer. Should be the accepted. Please bring actual code samples to the answer to make it clearer. – Maxim Veksler Feb 01 '18 at 08:55
  • 4
    For some reason, I'm getting the error message of `ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()` even though I'm using pytest version 4.3.0 – Allen Wang Mar 05 '19 at 00:19
  • 16
    @AllenWang it is likely that one or both of your values for comparison are numpy arrays. It seems in that case pytest would test for the truth value of numpy array resulting from the comparison, which is a numpy array of boolean values, and numpy would yield the above warning. Try wrapping both values with `list()`. – Pomin Wu Mar 21 '19 at 04:29
61

You could do a list comprehension to check equality of all values. If you call all on the list comprehensions result, it will return True if all parameters are equal.

actual = ['bl', 'direction', 'day']
expected = ['bl', 'direction', 'day']

assert len(actual) == len(expected)
assert all([a == b for a, b in zip(actual, expected)])

print(all([a == b for a, b in zip(actual, expected)]))

>>> True
Denis Barmenkov
  • 2,276
  • 1
  • 15
  • 20
ritchie46
  • 10,405
  • 1
  • 24
  • 43
  • @smerlung all() takes an iterable, the list comparison would return a bool. – Thomas T Feb 23 '18 at 15:48
  • 7
    You've forgot about list length check. zip() function cut output by list with minimal size. rewrite actual and set it to ['bl', 'direction', 'day', 'MYAHAHA'] or even [] and get True too :P I'll try to rewrite your sample. – Denis Barmenkov Oct 08 '20 at 17:08
38

There are two keys to answer this seemingly simple answer:
conditions of equality and assertion error helpfulness.

The conditions of equality depend on given constrains and requirements. Assertion error should point to the violation of those conditions.

Answer the following questions:

  1. Is order of lists important?

    are [1, 2] and [2, 1] equal?

  2. Can lists have duplicates?

    are [1, 2] and [1, 1, 2] equal?

  3. Are there unhashable elements?

    are there any mutable types?

  4. Should assertion error be informative and helpful?

No, No, No, No: symmetric difference between sets

def test_nnnn():
    expected = [1, 2, 3]
    actual = [4, 3, 2, 1]
    difference = set(a) ^ set(b)
    assert not difference

E assert not {4, 5}

It is handy to use this method on large lists, because it's fast and difference will contain only hm difference between them, so AssertionError will be compact, however not informative.

No, No, No, Yes: difference between sets with custom message

def test_nnny():
    expected = [1, 2, 3, 4]
    actual = [5, 3, 2, 1]
    lacks = set(expected) - set(actual)
    extra = set(actual) - set(expected)
    message = f"Lacks elements {lacks} " if lacks else ''
    message += f"Extra elements {extra}" if extra else ''
    assert not message

E AssertionError: assert not 'Lacks elements {4} Extra elements {5}'

No, Yes, No, Yes: check for duplicates, than difference in sets

Because set() removes duplicates you should check for them ahead using this answer:

def test_nyny():
    expected = [1, 2, 3, 4]
    actual = [1, 2, 3, 3, 5]

    seen = set()
    duplicates = list()
    for x in actual:
        if x in seen:
            duplicates.append(x)
        else:
            seen.add(x)

    lacks = set(expected) - set(actual)
    extra = set(actual) - set(expected)
    message = f"Lacks elements {lacks} " if lacks else ''
    message += f"Extra elements {extra} " if extra else ''
    message += f"Duplicate elements {duplicates}" if duplicates else ''
    assert not message

E AssertionError: assert not 'Lacks elements {4} Extra elements {5} Duplicate elements [3]'

Yes, Yes, Yes, No: compare lists

def test_yyyn():
    expected = [1, 2, 3, 3, 3, {'a': 1}]
    actual = [3, 3, 2, 1, {'a': 1}]
    assert expected == actual

E AssertionError: assert [1, 2, 3, 3, 3, {'a': 1}] == [3, 3, 2, 1, {'a': 1}]

Yes, Yes, Yes, Yes: extra libraries

Take a look at DeepDiff

ggguser
  • 1,872
  • 12
  • 9
20

If you are using the built in unittest.TestCase, there is already a method that can do that for you: unittest.TestCase.assertListEqual if you care about the list ordering, and unittest.TestCase.assertCountEqual if you don't.

https://docs.python.org/3.5/library/unittest.html#unittest.TestCase.assertCountEqual

  • 2
    I'm using pytest, not the builtin unittest package. That's why I'm not using assertListEqual.. – bAN Oct 25 '17 at 08:48
  • 7
    Calling `unittest.TestCase().assertCountEqual(first, second)` in a pytest testcase works just fine. You will get somewhat worse error message (with traceback from within the assertCountEqual method) if the assert fails, but otherwise there is no problem with that. – user7610 Jul 29 '18 at 21:05
  • This is the best answer for the general case with unmeaningful order, potential duplicates, potentially unsortable data records, potentially different but comparable but unsortable data types. See also https://stackoverflow.com/a/45946306/11715259 – N1ngu Feb 03 '22 at 11:46
12

In Python 3.9, this should now work:

def test_arrays_equal():
    a = [1, 2, 3]
    b = [1, 2, 4]
    assert a == b

Or, you can parse the lists to numpy arrays and use function array_equal:

import numpy as np

def test_arrays_equal():
    a = [1, 2, 3]
    b = [1, 2, 4]
    ar = np.array(a)
    br = np.array(b)
    assert np.array_equal(ar, br)
Adam Erickson
  • 6,027
  • 2
  • 46
  • 33
0

convert the numpy arrays to python lists and you'll get a better response than simply using all or any. This way, if the test fails you'll see how the expected vs actual differ

salah
  • 439
  • 4
  • 7
0

You could also use https://github.com/AdityaSavara/UnitTesterSG which can be obtained by pip UnitTesterSG

AdityaS
  • 31
  • 6