Merge lp:~rvb/testtools/testtools-contains-all into lp:~testtools-committers/testtools/trunk

Proposed by Raphaël Badin
Status: Merged
Merged at revision: 261
Proposed branch: lp:~rvb/testtools/testtools-contains-all
Merge into: lp:~testtools-committers/testtools/trunk
Diff against target: 72 lines (+29/-1)
2 files modified
testtools/matchers.py (+11/-0)
testtools/tests/test_matchers.py (+18/-1)
To merge this branch: bzr merge lp:~rvb/testtools/testtools-contains-all
Reviewer Review Type Date Requested Status
Jonathan Lange Needs Fixing
Review via email: mp+104065@code.launchpad.net

Commit message

New matcher ContainsAll.

Description of the change

This branch adds a new (shortcut) matcher ContainsAll. ContainsAll(items) that can be used instead of using MatchesAll(*[Contains(item) for item in items])). We use it quite a lot in MAAS so we figured we could submit it upstream.

Drive-by fix: removed unused import.

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

Thanks for the patch, Raphaël.

I don't quite understand the matcher though. From the docstring and the name, I would have guess something like this::

  self.assertThat([1, 2, 3], ContainsAll([3, 1]))

That is, self.assertThat(Y, ContainsAll(X)) would pass if and only if X is a subset of Y.

As it is, self.assertThat(Y, ContainsAll(X)) passes iff for all y in Y, for all x in X, x in y. (i.e. ∀ y ∊ Y, ∀ x ∊ X, 'x in y')

So, to me, the name is confusing and my lazy imagination isn't leaping to actual use cases.

Where to from here?

 * Demonstrate use cases

 * Pick a better name, maybe EachContainsAll

Follow up work for testtools more generally:

 * some kind of more generic helper might be interesting, e.g.
     lambda matcher, items: MatchesAll(*[matcher(item) for item in items], first_only=False)
   I don't really know what it ought to be called. Sorry.

 * MatchesAll is really an "And"-style function. It would probably be better if its name reflected that.

 * More generally, thinking a little bit more about predicate & set algebra might provide us with some better combinators

Thanks,
jml

review: Needs Fixing
Revision history for this message
Raphaël Badin (rvb) wrote :

> Thanks for the patch, Raphaël.
>
> I don't quite understand the matcher though. From the docstring and the name,
> I would have guess something like this::
>
> self.assertThat([1, 2, 3], ContainsAll([3, 1]))
>
> That is, self.assertThat(Y, ContainsAll(X)) would pass if and only if X is a
> subset of Y.

Indeed, that's precisely what this matcher does. Again, it's simply a shortcut, it uses existing functionality.

> As it is, self.assertThat(Y, ContainsAll(X)) passes iff for all y in Y, for
> all x in X, x in y. (i.e. ∀ y ∊ Y, ∀ x ∊ X, 'x in y')

Maybe I'm missing something but that's not what this matcher does. I can't resist using these nice utf8 characters:
self.assertThat(y, ContainsAll(X)) passes iff for all x in X, x in y. (i.e. ∀ x ∊ X, 'x in y')

Revision history for this message
Jonathan Lange (jml) wrote :

OK, so this does behave as like a subset matcher. Oops.

<jml> rvba: the problem was that I read this:
 + matches_matches = ['foobar', 'foozbar', 'bar foo']
 as this:
 + matches_matches = [['foobar', 'foozbar', 'bar foo']]

Raphaël, could you please update the tests to not rely on string containment -- I think that would be clearer. e.g.

 + matches_matches = [['foo', 'bar'], ['foo', 'z', 'bar'], ['bar', 'foo']]

Also if you could add another sentence to the class docstring using the word 'subset' somewhere that'd be great.

jml

259. By Raphaël Badin

Refactor tests. Improve doc.

Revision history for this message
Raphaël Badin (rvb) wrote :

> Raphaël, could you please update the tests to not rely on string containment
> -- I think that would be clearer. e.g.
>
> + matches_matches = [['foo', 'bar'], ['foo', 'z', 'bar'], ['bar', 'foo']]

Sure, I've refactored matches_matches and matches_mismatches in TestContainsAllInterface to not rely on string containment.

> Also if you could add another sentence to the class docstring using the word
> 'subset' somewhere that'd be great.

Done. Feel free to suggest something else for the docstring if the one I put doesn't suit you.

Revision history for this message
Raphaël Badin (rvb) wrote :

Hi Jonathan,

If you think this is worth it, could you please have another look at this? I think I've made all the improvements you requested.

R.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'testtools/matchers.py'
2--- testtools/matchers.py 2012-04-27 13:41:16 +0000
3+++ testtools/matchers.py 2012-04-30 14:41:18 +0000
4@@ -16,6 +16,7 @@
5 'AllMatch',
6 'Annotate',
7 'Contains',
8+ 'ContainsAll',
9 'DirExists',
10 'DocTestMatches',
11 'EndsWith',
12@@ -664,6 +665,16 @@
13 return None
14
15
16+def ContainsAll(items):
17+ """Make a matcher that checks whether a list of things is contained
18+ in another thing.
19+
20+ The matcher effectively checks that the provided sequence is a subset of
21+ the matchee.
22+ """
23+ return MatchesAll(*map(Contains, items), first_only=False)
24+
25+
26 class StartsWith(Matcher):
27 """Checks whether one string starts with another."""
28
29
30=== modified file 'testtools/tests/test_matchers.py'
31--- testtools/tests/test_matchers.py 2011-12-21 01:28:38 +0000
32+++ testtools/tests/test_matchers.py 2012-04-30 14:41:18 +0000
33@@ -12,7 +12,6 @@
34
35 from testtools import (
36 Matcher, # check that Matcher is exposed at the top level for docs.
37- skipIf,
38 TestCase,
39 )
40 from testtools.compat import (
41@@ -29,6 +28,7 @@
42 AnnotatedMismatch,
43 _BinaryMismatch,
44 Contains,
45+ ContainsAll,
46 DirContains,
47 DirExists,
48 DocTestMatches,
49@@ -409,6 +409,23 @@
50 describe_examples = [("1 not in 2", 2, Contains(1))]
51
52
53+class TestContainsAllInterface(TestCase, TestMatchersInterface):
54+
55+ matches_matcher = ContainsAll(['foo', 'bar'])
56+ matches_matches = [['foo', 'bar'], ['foo', 'z', 'bar'], ['bar', 'foo']]
57+ matches_mismatches = [['f', 'g'], ['foo', 'baz'], []]
58+
59+ str_examples = [(
60+ "MatchesAll(Contains('foo'), Contains('bar'))",
61+ ContainsAll(['foo', 'bar'])),
62+ ]
63+
64+ describe_examples = [("""Differences: [
65+'baz' not in 'foo'
66+]""",
67+ 'foo', ContainsAll(['foo', 'baz']))]
68+
69+
70 def make_error(type, *args, **kwargs):
71 try:
72 raise type(*args, **kwargs)

Subscribers

People subscribed via source and target branches