← Back to team overview

launchpad-dev team mailing list archive

Re: The start of the Matchers invasion

 

On Wed, 04 Aug 2010 16:30:13 -0400, James Westby <james.westby@xxxxxxxxxxxxx> wrote:
> Hi,
> 
> testtools and assertThat have been available for a while now, but I
> just landed what I think are the first uses of them in the launchpad
> tree.
> 

> Yes, these are fairly similar to custom assertion methods, and while
> they may seem a little more heavyweight, they do have some advantages:
> 
>   * They can be composed in a more pleasant fashion.
>   * They can be used outside of testing.
>   * They are easier to test.
>   * They can provide better failure messages.
>   * They are outside the TestCase class, so you don't have to load your
>     base TestCase class, or have many different ones and then have
>     difficulty when you want to combine two or more.

Oh, I forgot one of the coolest things about matchers.

Rob just landed this matcher:

class HasQueryCount(Matcher):
    """Adapt a Binary Matcher to the query count on a QueryCollector.

    If there is a mismatch, the queries from the collector are provided
    as a
    test attachment.
    """

    def __init__(self, count_matcher):
        """Create a HasQueryCount that will match using
        count_matcher."""
        self.count_matcher = count_matcher

    def __str__(self):
        return "HasQueryCount(%s)" % self.count_matcher

    def match(self, something):
        mismatch = self.count_matcher.match(something.count)
        if mismatch is None:
            return None
        return _MismatchedQueryCount(mismatch, something)


class _MismatchedQueryCount(Mismatch):
    """The Mismatch for a HasQueryCount matcher."""

    def __init__(self, mismatch, query_collector):
        self.count_mismatch = mismatch
        self.query_collector = query_collector

    def describe(self):
        return "queries do not match: %s" %
        (self.count_mismatch.describe(),)

    def get_details(self):
        result = []
        for query in self.query_collector.queries:
            result.append(unicode(query).encode('utf8'))
        return {'queries': Content(ContentType('text', 'plain',
            {'charset': 'utf8'}), lambda:['\n'.join(result)])}


The interesting thing is the last method, Mismatch.get_details().

If this mismatch causes an assertion failure then testtools will call
that method on it, and if it returns anything it will automatically call
addDetail() on the TestCase, to add the information to the test result.

What this means is that you can write a small bit of code on your
Mismatches that will ensure that everytime a test fails because of the
mismatch, the user can access certain information. Here it is listing
all of the queries, but it could tell you the state of all packages in
an archive, the state of a bug, the content of a HTTP response, whatever
you like.

This means that you don't have to dump everything in the assertion
message, making them easier to read, while still allowing developers to
access information about the state of the system at the time of failure.

Thanks,

James



References