if + else 和 if -> 之间不一致,除非



我今天在定义自定义RSpec匹配器时遇到了一个问题,我解决了这个问题,但实际上看不出其中一种方法有效而另一种方法无效的原因,下面是代码:

方法1-if+else:

RSpec::Matchers.define :have_success_message do |message|
match do |page|
if message.nil?
page.should have_selector('div.alert.alert-success')
else
page.should have_selector('div.alert.alert-success', text: message)
end
end
end

方法2——如果后面跟着除非

RSpec::Matchers.define :have_success_message do |message|
match do |page|
page.should have_selector('div.alert.alert-success') if message.nil?
page.should have_selector('div.alert.alert-success', text: message) unless message.nil?
end
end

我认为第一种方法更好,因为它只检查一次条件,但无论如何,结果应该是一样的,对吧?

事实证明,第一种方法的测试通过了,而第二种方法的检测没有通过。我完全不知道为什么会这样,如果有人能对此有所了解,我会很高兴的。

编辑:

忘记添加实际测试(使用方法2):

存在以下HTML标记:

<div class="alert alert-success">Profile updated</div>

我运行了4个单独的测试:

it { should have_success_message } # fails
it { should have_success_message('Profile updated') } # passes
it { should have_selector('div.alert.alert-success') } # passes
it { should have_selector('div.alert.alert-success', text: "Profile updated") } # passes

故障显示以下消息:

1) User pages edit with valid information 
Failure/Error: it { should have_success_message }
expected #<Capybara::Session> to have success message
# ./spec/requests/user_pages_spec.rb:80:in `block (5 levels) in <top (required)>'

当HTML标记不存在时,所有4个测试都会失败。

编辑2:

我尝试了另一种方法来验证控制流是否正确:

方法3:

if message.nil?
puts "In if, message is: #{message.inspect}"
page.should(have_selector('div.alert.alert-success'))
end
unless message.nil?
puts "In unless, message is: #{message.inspect}"
page.should(have_selector('div.alert.alert-success', text: message))
end

这种方法的行为与方法2相同——第一次测试失败,然后通过3次

输出如下:

在if中,消息为:nil
在excess中,消息是:"Profile updated">

所以控制流程看起来不错,但

page.should(have_selector('div.alert.alert-success'))

失败,即使它通过了比赛者之外。这确实是个谜。

最终编辑:

只是为了响应批准的答案-当我像这样切换代码时:

page.should have_selector('div.alert.alert-success', text: message) unless message.nil? 
page.should have_selector('div.alert.alert-success') if message.nil?

测试如下:

it { should have_success_message } # passes
it { should have_success_message('Profile updated') } # fails
it { should have_selector('div.alert.alert-success') } # passes
it { should have_selector('div.alert.alert-success', text: "Profile updated") } # passes

所以我认为,事实上,最后一行,当它不是真的时,会被评估为零,这导致了整个混乱。无论如何,第一种方法更好,但我很高兴我已经忘记了这个问题:)

这是RSpec的正确行为,尽管它看起来出乎意料。

考虑这个代码:

x = nil
"foo" if x.nil?
"bar" unless x.nil?
#=> 
"foo"
nil

当条件为false时,...unless语句返回nil

在自定义匹配器中,当消息为nil时,...unless语句返回nil。

这是你比赛盖帽中的最后一行,所以你的比赛盖帽返回零。

然后RSpec看到您的匹配块返回nil,RSpec认为这与false相同,因此RSpec报告您的自定义匹配器失败。

哇,真是一个绝妙的谜题!关键是match方法需要返回一个布尔结果。在第一个选项中,隐式返回值是if分支的结果,即true

为什么是trueshould是这样定义的:

def should(matcher=nil, message=nil, &block)
::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)
end

它委托给PositiveExpectationHandler,其handle_matcher方法如下所示:

def self.handle_matcher(actual, matcher, message=nil, &block)
check_message(message)
::RSpec::Matchers.last_should = :should
::RSpec::Matchers.last_matcher = matcher
return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) if matcher.nil?
match = matcher.matches?(actual, &block)
return match if match
message ||= matcher.respond_to?(:failure_message_for_should) ?
matcher.failure_message_for_should :
matcher.failure_message
if matcher.respond_to?(:diffable?) && matcher.diffable?
::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
else
::RSpec::Expectations.fail_with message
end
end

我们可以看到,如果matcher返回true(或者实际上是任何truthy值),这是从函数返回的,并隐式地成为should的返回值。我不确定这种行为是否在任何地方都有记录。

然而,在第二个选项中,最后一个表达式/语句的值获胜,并且由于unless条件为true,因此该表达式的求值结果为nil:

1.9.3-p0 :001 > x unless true
=> nil 

所以RSpec认为匹配器试图报告失败,因为您意外返回了nil


要解决此问题,您可能不应该在匹配器中使用should。你可以这样打电话给Capybara的HaveSelector匹配器:

if message.nil?
have_selector('div.alert.alert-success').matches? page
else
have_selector('div.alert.alert-success', text: message).matches? page
end

顺便说一句,指定text: nil最终会导致regexp为空,因此无论如何都不需要检查nil,您可以这样写匹配器:

match do |page|
have_selector('div.alert.alert-success', text: message).matches? page
end

我承认,不太像鲁比耶斯克,但你做到了。

最新更新