7

So I was having an issue with unittest results failing due to the test claiming that an assertEqual statement was failing.

Error code:

AssertionError: {1: ((51, 98), (31, 4)), 2: ((51, 98), (49, 80)), 3: ((31, 4), (49, 80))} != {1: ((51, 98), (31, 4)), 2: ((51, 98), (49, 80)), 3: ((31, 4), (49, 80))}

Here's the unittest code below:

class InstanceTestCase(unittest.TestCase):

    def setUp(self):
        self.prob = Generate.populated_instance(3, 12345678)

        node_one = Node(51, 98)
        node_two = Node(31, 4)
        node_three = Node(49, 80)

        edge_one = Edge(node_one, node_two)
        edge_two = Edge(node_one, node_three)
        edge_three = Edge(node_two, node_three)

        self.comparison_edges = {1: edge_one, 2: edge_two, 3: edge_three}

    def test_Instance(self):
        self.assertEqual(self.prob.edges, self.compare_edges_dict)  # returning an error

which is testing these classes:

class Node:
    """Represents the nodes as points with a position x, y."""

    def __init__(self, x=0, y=0):
        """Create a new node at x, y."""
        self.x = x
        self.y = y

    def __str__(self):
        return "({0}, {1})".format(self.x, self.y)

    def __repr__(self):
        return str(self)

    def __eq__(self, other):
        if type(self) == type(other):
            return self.x == other.x and self.y == other.y
        else:
            raise TypeError

    def __hash__(self):
        return hash((self.x, self.y))


class Edge:
    """Represents the edges as having a length and being bounded by two points."""
    def __init__(self, node_one, node_two):
        """Create an edge using two nodes."""
        self.node_one = node_one
        self.node_two = node_two

    def __str__(self):
        return "({0}, {1})".format(self.node_one, self.node_two)

    def __repr__(self):
        return str(self)


class Instance:

    count = 0

    def __init__(self, node_count=0, seed=0, nodes=None, edges=None, solution_path=None, solve_time=0):
        """Create a new problem with x number of nodes.
        Leave seed blank for random seed, otherwise put an eight digit number to use for the seed."""
        self.node_count = node_count
        self.seed = seed
        self.nodes = nodes
        self.edges = edges
        self.solution_path = solution_path
        self.solve_time = solve_time
        Instance.count += 1

    # makes outputs from print easier to read
    def __str__(self):
        return "ID: {0}\n" \
               "Node count: {1}\n" \
               "Seed: {2}\n" \
               "Nodes: {3}\n" \
               "Edges: {4}\n" \
               "Solve time: {5}".format(self.problem_id,
                                        self.node_count,
                                        self.seed,
                                        self.nodes,
                                        self.edges,
                                        self.solve_time)

    def __repr__(self):
        return str(self)

The prob object being created in the testing code is an instance of the Instance class, which, among other things, contains two dictionaries (after a method call), one for the nodes and one for the edges. The nodes are generated using the populated_instance method of the Generate class, and the edges are generated from the nodes by different method call within the populated_instance method.

The prob.edges dictionary looks like this:

{1: ((51, 98), (31, 4)), 2: ((51, 98), (49, 80)), 3: ((31, 4), (49, 80))}

and is made up of instances of the Node class contained within instances of the Edge class. As you can see in the unittest code above, I've created an equivalent dictionary using the same objects with the same values.

So, I knew for sure that the prob.edges dictionary and the comparison_edges dictionary were identical, were entirely made up of properly instantiated class objects, and that I wasn't comparing any of these objects to strings that looked like objects.

I found this SO question about a similar problem: Python unittest failure when results appear equal... however it shed no light on my issue.

After an hour or so I eventually realised what was happening, and it was a different problem entirely. So I'm going to post my question and the answer as well.

2
  • Good stuff. Welcome to stackoverflow. Commented Aug 17, 2018 at 16:21
  • Thanks :) This is the first time I've been actually been able to contribute with something that wasn't already answered :) Commented Aug 17, 2018 at 16:25

2 Answers 2

5

Without an own __eq__() method Python compares just the IDs of your objects and they are of course not equal. If you want to test equality of the attributes of different objects you need the __eq__() method. In fact you could compare in this method whatever you want. I had an example where equality was defined as equality of two out of five attributes. The remaining three attributes were not considered.

By the way: in your __eq__() method you should check the equality of the types of your objects as well. Imagine two instances of different classes which have at random the same attributes ...

Sign up to request clarification or add additional context in comments.

Comments

3

The answer in my case was that I had not implemented an __eq__() method in my Edge class.

This method will be called automatically by some other methods, and if it is not implemented in your class then the methods that require an equality check may not function correctly.

My Edge class originally looked like this:

class Edge:
    """Represents the edges as having a length and being bounded by two points."""
    def __init__(self, node_one, node_two):
        """Create an edge using two nodes."""
        self.node_one = node_one
        self.node_two = node_two

    def __str__(self):
        return "({0}, {1})".format(self.node_one, self.node_two)

    def __repr__(self):
        return str(self)

and after including this...

    def __eq__(self, other):
        if type(self) == type(other):
            return self.node_one == other.node_one and self.node_two == other.node_two
        else:
            raise TypeError

the equality check used in assertEqual worked fine.

(Note: I had already implemented an overridden __eq__() method in my Node class, and testing confirmed that removing that also broke the above check.)

Looking at this from the python docs (https://docs.python.org/2/library/unittest.html):

assertEqual(first, second, msg=None) Test that first and second are equal. If the values do not compare equal, the test will fail.

In addition, if first and second are the exact same type and one of list, tuple, dict, set, frozenset or unicode or any type that a subclass registers with addTypeEqualityFunc() the type-specific equality function will be called in order to generate a more useful default error message (see also the list of type-specific methods).

Changed in version 2.7: Added the automatic calling of type-specific equality function.

it appears to me that 'the type-specific equality function' is being called on the custom class, and therefore, if a custom __eq__() method does not exist then the check will fail.

See answer below by @Humbalan for further details :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.