Skip to content

object comparison with pytest produces output that isn't very useful #2205

@ITProKyle

Description

@ITProKyle

Opening to remind myself to contribute this when I get a chance unless someone beats me to it but also to discuss final formatting.

Using the following example code:

"""Example."""
from troposphere import Tag, Tags


def test_tags() -> None:
    """Test tag comparison."""
    assert Tags(Tag("a-tag", "test"), Tag("b-tag", "test")) == Tags(
        Tag("a-tag", "test"), Tag("b-tag", "fail")
    )

pytest will output the following for the failed assertion:

    def test_tags() -> None:
        """Test tag comparison."""
>       assert Tags(Tag("a-tag", "test"), Tag("b-tag", "test")) == Tags(
            Tag("a-tag", "test"), Tag("b-tag", "fail")
        )
E       AssertionError: assert <troposphere.Tags object at 0x1108fda90> == <troposphere.Tags object at 0x1108ff550>
E        +  where <troposphere.Tags object at 0x1108fda90> = Tags(<troposphere.Tag object at 0x110911ed0>, <troposphere.Tag object at 0x1108ff990>)
E        +    where <troposphere.Tag object at 0x110911ed0> = Tag('a-tag', 'test')
E        +    and   <troposphere.Tag object at 0x1108ff990> = Tag('b-tag', 'test')
E        +  and   <troposphere.Tags object at 0x1108ff550> = Tags(<troposphere.Tag object at 0x1108fdd10>, <troposphere.Tag object at 0x1108fd450>)
E        +    where <troposphere.Tag object at 0x1108fdd10> = Tag('a-tag', 'test')
E        +    and   <troposphere.Tag object at 0x1108fd450> = Tag('b-tag', 'fail')

This does not give a ton of easily useful info about where the comparison failed. It can be determined for this simple example because everything is defined in the test but for large, complex comparison it is not user friendly.
This experience can be improved by adding a __repr__ method to the classes.

class BaseAWSObject:

	def __repr__(self) -> str:
        return f"<{self.__module__}.{self.__class__.__name__}({self.to_dict()}) object at {hex(id(self))}>"


class AWSHelperFn:

    def __repr__(self) -> str:
        return f"<{self.__module__}.{self.__class__.__name__}({self.to_dict()}) object at {hex(id(self))}>"

This would produce an output of:

    def test_tags() -> None:
        """Test tag comparison."""
>       assert Tags(Tag("a-tag", "test"), Tag("b-tag", "test")) == Tags(
            Tag("a-tag", "test"), Tag("b-tag", "fail")
        )
E       AssertionError: assert <troposphere.Tags([{'Key': 'a-tag', 'Value': 'test'}, {'Key': 'b-tag', 'Value': 'test'}]) object at 0x103653a50> == <troposphere.Tags([{'Key': 'a-tag', 'Value': 'test'}, {'Key': 'b-tag', 'Value': 'fail'}]) object at 0x103653b10>
E        +  where <troposphere.Tags([{'Key': 'a-tag', 'Value': 'test'}, {'Key': 'b-tag', 'Value': 'test'}]) object at 0x103653a50> = Tags(<troposphere.Tag({'Key': 'a-tag', 'Value': 'test'}) object at 0x1036539d0>, <troposphere.Tag({'Key': 'b-tag', 'Value': 'test'}) object at 0x103653810>)
E        +    where <troposphere.Tag({'Key': 'a-tag', 'Value': 'test'}) object at 0x1036539d0> = Tag('a-tag', 'test')
E        +    and   <troposphere.Tag({'Key': 'b-tag', 'Value': 'test'}) object at 0x103653810> = Tag('b-tag', 'test')
E        +  and   <troposphere.Tags([{'Key': 'a-tag', 'Value': 'test'}, {'Key': 'b-tag', 'Value': 'fail'}]) object at 0x103653b10> = Tags(<troposphere.Tag({'Key': 'a-tag', 'Value': 'test'}) object at 0x103653a90>, <troposphere.Tag({'Key': 'b-tag', 'Value': 'fail'}) object at 0x103653ad0>)
E        +    where <troposphere.Tag({'Key': 'a-tag', 'Value': 'test'}) object at 0x103653a90> = Tag('a-tag', 'test')
E        +    and   <troposphere.Tag({'Key': 'b-tag', 'Value': 'fail'}) object at 0x103653ad0> = Tag('b-tag', 'fail')

While it still does not show the exact item in the object that is failing, it does provide the data contained within the objects in the AssertionError.


The example implementation provided here mirrors the <... object at ...> notation of object.__repr__(). Should formatting be retained or should any part of it be dropped <e.g. just the <> or just object at ...) or should the formatting remain as it is in the example above?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions