James CookeJames Cooke

Comparing Django Q Objects in Python 3 with pytest

Background

In a previous post I wrote about comparing Django’s Q object instances. The original code was Python 2 with unittest and was due for an update.

The previous issue with comparing Django’s Q objects remains the same:

Django’s Q object does not implement __cmp__ and neither does Node which it extends (Node is in the django.utils.tree module).

Unfortunately, that means that comparison of Q objects that are equal fails.

A simple Python 3 solution

The following is a Python 3.6 assertion helper for use with pytest that uses the original strategy of comparing the string versions of the Q objects.

from django.db.models import Q


def assert_q_equal(left, right):
    """
    Test two Q objects for equality. Does is not match commutative.

    Args:
        left (Q)
        right (Q)

    Raises:
        AssertionError: When -
            * `left` or `right` are not an instance of `Q`
            * `left` and `right` are not considered equal.
    """
    assert isinstance(left, Q), f'{left.__class__} is not subclass of Q'
    assert isinstance(right, Q), f'{right.__class__} is not subclass of Q'
    assert str(left) == str(right), f'Q{left} != Q{right}'

This time the helper is just a function rather than a mixin for unittest.TestCase.

isinstance is used for comparison so that any instance of a class derived from Q can also be matched. The assertions have secondary expressions in the form of f-strings to give helpful output without raising a custom assertion.

When two Q instances do not match, pytest shows the following output:

______________________ test_neq_multi_not_commutative ______________________
test_assert_q_equal.py:83: in test_neq_multi_not_commutative
    assert_q_equal(q_a, q_b)
test_assert_q_equal.py:22: in assert_q_equal
    assert str(left) == str(right), f'Q{left} != Q{right}'
E   AssertionError: Q(AND: ('speed', 12), ('direction', 'north')) != Q(AND: ('direction', 'north'), ('speed', 12))
E   assert "(AND: ('spee...n', 'north'))" == "(AND: ('direc...'speed', 12))"
E     - (AND: ('speed', 12), ('direction', 'north'))
E     + (AND: ('direction', 'north'), ('speed', 12))
==================== 1 failed, 7 passed in 0.07 seconds ====================

The important thing is to adjust your assertion helpers to best fit the needs of your test suite and team.


  • Django Factory Audit

    Exploring the various model instance factories available for Django. Each factory is tested based on the quality of the instances it creates. Bonus: talk version and slides also available.

  • Comparing Django Q Objects

    A super simple assertion helper for comparing instances of Django’s Q objects.

  • Arrange Act Assert pattern for Python developers

    This post introduces the Arrange Act Assert pattern of testing and shows how it can be used in a Python context with Pytest.