-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Negating the matcher #15
Conversation
@tklarryonline @steadykhoi @nhanbt @hotay please help taking a look on this one. |
robber/expect.py
Outdated
self.to = self | ||
self.be = self | ||
self.a = self | ||
self.an = self | ||
self.have = self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have self.is_negated = False
for each property here? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@anhhuy1605 Not yet, I think. I will think about it again when I consider about the double (triple...) negated matcher.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there should be a not
property that trigger is_negated
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a function something like negate
to toggle the is_negated
to handle the double/triple... negating as well, it will come in the next commit.
robber/expect.py
Outdated
.match() | ||
klass(self.obj, other, self.is_negated, *args) \ | ||
.fail_with(self.message) \ | ||
.match() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's with the outrageous indentation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this indentation is correct, the previous code of robber.py
didn't use PEP8 or flake8 and I fixed this when I see the warnings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to declare a method here, rather than making a big lambda like this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tklarryonline It's old code, I just keep it the same to make the proof of concept work first. Will do the refactor later.
robber/expect.py
Outdated
@property | ||
def not_to(self): | ||
self.is_negated = True | ||
return self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we have a not
property then this is not needed. Since you could just write .not.to
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not
is Python keyword, I think that we should avoid it.
tests/test_integration.py
Outdated
expect(1).not_to.eq(2) | ||
expect([1, 2]).not_to.eq([2, 1]) | ||
expect((1, 2)).not_to.eq((2, 1)) | ||
expect(1).not_to == 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The grammatically correct way to write these is:
expect(<something>).to.not.eq(<something>)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to_not
and not_to
are both correct, but not_to
is more popular, see the discussion here: #13
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, I was just about to say the same thing.
robber/matchers/base.py
Outdated
self.args = args | ||
|
||
def fail_with(self, message): | ||
self.message = message | ||
return self | ||
|
||
def match(self): | ||
if not self.matches(): | ||
if not self.matches() and not self.is_negated: | ||
message = self.message or self.failure_message() | ||
raise BadExpectation(message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about self.matches() and self.is_negated
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be, I made it quick to see if it works, the big refactor will be coming to address the remaining issues that I listed in the PR description.
.gitignore
Outdated
@@ -34,3 +34,5 @@ nosetests.xml | |||
.mr.developer.cfg | |||
.project | |||
.pydevproject | |||
.python-version | |||
.idea/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a blank line here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to see your comment here @catriuspham. Yes, you are right, I will add it soon.
tests/matchers/test_numbers.py
Outdated
expect(Within(1, 0, 2).matches()) == True | ||
expect(Within(2, 3, 4).matches()) == False | ||
expect(Within(1, 0, False, 2).matches()) == True | ||
expect(Within(2, 3, False, 4).matches()) == False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the boolean value should be at the end or the beginning of the variable set. Like:
expect(Within(1, 0, 2, False).matches()) == True
or expect(Within(False, 1, 0, 2).matches()) == True
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@catriuspham You are right, but I think we can't do it here. Because this Within
was extended from Base
, which is actually defined like this:
class Base:
def __init__(actual, expected, is_negated, *args):
pass
The last value of Within
is actually the agrs[0]
, then we can't control it.
robber/matchers/boolean.py
Outdated
@@ -11,7 +11,9 @@ def matches(self): | |||
return self.actual == True | |||
|
|||
def failure_message(self): | |||
return 'Expected %s to be True' % self.actual | |||
negated_message = ' not' if self.is_negated else '' | |||
return 'Expected %s%s to be True' % (self.actual, negated_message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about moving the space to between the %s
, like this:
negated_message = 'not' if self.is_negated else ''
return 'Expected %s %s to be True' % (self.actual, negated_message)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we move the space into the %s, when we don't have not_to, it will be something like this:
Expected False to be true
When we just want this:
Expected False to be true
Please resolve the conflicts for this pull request. Thanks. |
@oyster This PR is not mergeable yet, we still have a lot of things to do, I will update it in the PR description later. |
Thanks, please update and add a label like WIP to this pull request. |
@oyster I had that intention, but it looks like I don't have enough permission to do so. |
Ok, I created a new label |
How is it now, @hieueastagile ? |
@tklarryonline Still have no time for it :(. It still had a lot of things to do with the failure_message. |
I'm kind of free now, how can I contribute? |
@catriuspham Let's start with building this repo locally :) |
@catriuspham Let's start with set-up this repo in your local as @tklarryonline said. After that, you can look up to the issues, there's a lot of available stuff you can do there. If you need any help, feel free to raise it at #python channel. |
OK 😄 |
- Move negated_message into base - Remove unused __str__ function in BadExpectation - Use string format in failure massage - Change "to not" to "not to"
Now we can register a negative matcher using the positive one. The failure message need to be customized if needed. The benefit is that we don't have to create a whole new class for those negative matchers. - Replace False matcher with negative True matcher - Replace Exclude/NotContain matcher with negative Contain matcher - Replace Falsy matcher with negative Truthy matcher
robber/expect.py
Outdated
self.object = object | ||
def __init__(self, obj): | ||
self.obj = obj | ||
self.is_negative = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I guess is_negated
is actually a better name, guy @catriuspham :P.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hieueastagile Could you explain why? As I mentioned in this commit de70d8c
Base on OALD, "negative" means "containing a word such as 'no', 'not',
'never', etc.". Moreover, I cannot find the word "negated".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@catriuspham See this definition: http://www.dictionary.com/browse/negate. negated
is closer to the meaning of what we are going to do. I think you can find it if you take a look around to another assertion library also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hieueastagile well, you can take a look at the fourth meaning in OALD: http://www.oxfordlearnersdictionaries.com/definition/english/negative_1?q=negative.
It literally means "containing a word such as ‘no’, ‘not’, ‘never’, etc.".
Another source is Google Trends: https://trends.google.com/trends/explore?q=negative,negated
You can see that "negative" is more widely used, while the usage of "negated" is really limited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't say about the trend here, since negate
is not very popular, of course. But the meaning of it is something like the reversing the matcher's meaning, which is obviously closer to what we are going to do. Because logically, you can do something like not_to.not_to, it contains not
but actually it's not negative, it's just negated twice. Anyway, it's not too bad then.
robber/expect.py
Outdated
cls.matchers[name] = klass | ||
|
||
def method(self, other=None, *args): | ||
return klass(self.object, other, *args).fail_with(self.message).match() | ||
fact = is_negative is not self.is_negative |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you really want to check here @catriuspham ? is_negative != self.is_negative?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's take a look at how I registered the boolean matchers.
expect.register('true', TrueMatcher)
expect.register('false', TrueMatcher, is_negative=True)
self.is_negative
is the initial nature of the matcher. As for the TrueMatcher here, it it False
.
is_negative
is how we want to use it. As for the TrueMatcher registered as false
here, it it True
.
is_negative != self.is_negative
decide the final nature of the matcher. I will put a fact table here:
self.is_negative | is_negative | Fact |
---|---|---|
True | True | False |
True | False | True |
False | True | True |
False | False | False |
From this fact table, I got that formula.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I rather write a FalseMatcher to make it cleaner than re-use the TrueMatcher in this case. It seems magical to me. Anyway, do it your choice then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I agree with you that it's (really) magical. But only the implementation is complicated, and we only have to do it once. This way of registration is really easy to use, DRY and saves time. So in my opinion, the trade-off is acceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can do it your way, but FYI, explicit/easy to read/easy to understand is always prefer than DRY, especially for python code.
@catriuspham Could you do a list what you have finished and which not? (please ignore it if it takes you a lot of time). Then I can do help if I have some free time. |
@hieueastagile Actually, I have finished all the |
@hieueastagile As I have finished working on this PR, please help change the PR title into something like:
And remove the |
Anyway, there's a case the I didn't see it here: |
@hieueastagile I thought you agreed to let it be out there for fun... But yeah, that would lead to an overflow (in some case) so I think we could raise an exception for that. |
lol, up to @catriuspham then @tklarryonline |
@hieueastagile About the title, I mean we don't need that "First draft version" anymore, as this PR becomes really "serious" now 😂 |
@tklarryonline I tried to support the "fun stuff" but then realized that it was not so easy so... My apologies... |
- not_to.not_to.not_to.not_to.not_to.not_to.eq(something) is available - rewrite the registration logic to make it easier to understand
@tklarryonline I found out an easy way to make multiple negative chains work 😛 |
@catriuspham Great to know! 😀 Anyhow, I think @hieueastagile doesn't really like it... But yeah it's up to you hehe |
|
||
expect(1).not_to.ne(2) | ||
expect(1).not_to.not_to.not_to.ne(2) | ||
expect(1).not_to.not_to.not_to.not_to.not_to.ne(2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have to admit that reading this cause my little brain blows 😂, but it's fun figuring these chains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Legend says that if you read it out loud, you gonna become a rapper 🙄
robber/matchers/boolean.py
Outdated
|
||
|
||
expect.register('true', TrueMatcher) | ||
expect.register('false', FalseMatcher) | ||
expect.register('false', TrueMatcher, is_negative=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the name TrueMatcher
is quite confusing as this class also supports False cases...
- Rename TrueMatcher to Boolean
@NhanHo I think you want to take a look :) |
@NhanHo please take a look on this soon. Thanks |
@oyster please help merging this. |
Description
not_to
syntaxNote
Relevant issues