断言在Cucumber中有一个特殊的异常。

时间:2021-01-25 20:29:32

Scenario

场景

I'm writing a library (no Ruby on Rails) for which I'd like to have very detailed Cucumber features. This especially includes describing errors/exceptions that should be thrown in various cases.

我正在编写一个库(没有Ruby on Rails),我希望有非常详细的Cucumber特性。这特别包括描述在各种情况下应该抛出的错误/异常。

Example

例子

The most intuitive way to write the Cucumber steps would probably be something like

写黄瓜步骤最直观的方法大概是这样的

When I do something unwanted
Then an "ArgumentError" should be thrown

Problem

问题

There are two issues I have to address:

我必须解决两个问题:

  1. The first step should not fail when an exception is thrown.
  2. 当抛出异常时,第一步不应该失败。
  3. The exception that the first step throws should be accessible to the second step in order to do some assertion magic.
  4. 第一步抛出的异常应该可以访问到第二步,以便执行一些断言魔术。

Unelegant And Cumbersome Solution

Unelegant和繁琐的解决方案

The best approach I've been able to come up with is caching the exception in the first step and putting it into an instance variable that the second step can access, like so:

我能想到的最好的方法是在第一步缓存异常,并将其放入第二个步骤可以访问的实例变量中,如下所示:

When /^I do something unwanted$/ do
  begin
    throw_an_exception!
  rescue => @error
  end
end

Then /^an "(.*)" should be thrown$/ do |error|
  @error.class.to_s.should == error
end

However, this makes the first step more or less useless in cases where I don't want it to fail, and it requires an instance variable, which is never a good thing.

然而,在我不想让它失败的情况下,这使第一步或多或少的无用,并且它需要一个实例变量,这不是一件好事。

So, can anyone help me out with an at least less cumbersome solution? Or should I write my features differently anyway? Any help would be much appreciated.

那么,有没有人能帮我解决这个问题?还是我应该用不同的方式来写我的特性?如有任何帮助,我们将不胜感激。

4 个解决方案

#1


5  

I thought about it once more, and maybe the answer is:

我又想了一遍,也许答案是:

There is no elegant solution, because the Given-When-Then-Scheme is violated in your case. You expect that "Then an exception should be thrown" is the outcome of "When I do something unwanted".

这里没有优雅的解决方案,因为在您的案例中,给定时-当时-当时-计划被违反了。你期望“那么应该抛出一个异常”是“当我做了不需要的事情”的结果。

But when you think about it, this is not true! The exception is not the outcome of this action, in fact the exception just shows that the "When"-Statement failed.

但是当你仔细想想,这不是真的!异常并不是该操作的结果,事实上,异常只是表明“When”-语句失败。

My solution to this would be to test at a higher level:

我的解决办法是在更高的水平上进行测试:

When I do something unwanted
Then an error should be logged

or

When I do something unwanted
Then the user should get an error message

or

When I do something unwanted
Then the program should be locked in state "error"

or a combination of these.

或者两者的结合。

Then you would "cache the exception" in your program - which makes perfect sense, as you most likely need to do that anyway.

然后你会在你的程序中“缓存这个异常”——这很有意义,因为无论如何你都很可能需要这么做。

The two problems you've stated would be solved, too.

你所说的两个问题也会得到解决。

In case you really must test for exceptions

如果您确实必须测试异常

Well, i guess then cucumber isn't the right test suite, hmm? ;-)

嗯,我猜黄瓜不是合适的测试套件,嗯?:-)

As the Given-When-Then-Scheme is violated anyway, I would simply write

无论如何,当“给予时”计划被违反时,我只会写下来

When I do something unwanted it should fail with "ArgumentError"

and in the step definitions something like (untested, please correct me if you try it)

在步骤定义中(未经测试,请纠正我,如果你尝试)

When /^I do something unwanted it should fail with "(.*)"$/ do |errorstring|
  expect {
    throw_an_exception!
  }.to raise_error(errorstring)
end

As said above, that is horribly wrong as the scheme is broken, but it would serve the purpose, wouldn't it? ;-)

如上所述,这是一个可怕的错误,因为这个计划被打破了,但它将服务于目的,不是吗?:-)

You'll find further documentation at testing errors at rspec expectations.

您将在rspec期望的测试错误中找到进一步的文档。

#2


4  

One option is to mark the scenario with @allow-rescue and check the page's output and status code. For example

一个选项是使用@allow-rescue标记该场景,并检查页面的输出和状态代码。例如

In my_steps.rb

在my_steps.rb

Then(/^the page (?:should have|has) content (.+)$/) do |content|
  expect(page).to have_content(content)
end

Then(/^the page should have status code (\d+)$/) do |status_code|
  expect(page.status_code.to_s).to eq(status_code)
end

Then /^I should see an error$/ do
  expect(400..599).to include(page.status_code)
end

In my_feature.feature

在my_feature.feature

@allow-rescue
Scenario: Make sure user can't do XYZ
  Given some prerequisite
  When I do something unwanted
  Then the page should have content Routing Error
  And the page should have status code 404

or alternatively:

或者:

@allow-rescue
Scenario: Make sure user can't do XYZ
  Given some prerequisite
  When I do something unwanted
  Then I should see an error

This may not be exactly what you were hoping for, but it might be an acceptable workaround for some people who come across this page. I think it will depend on the type of exception, since if the exception is not rescued at any level then the scenario will still fail. I have used this approach mostly for routing errors so far, which has worked fine.

这可能不是您所希望的,但是对于一些浏览此页面的人来说,这可能是一个可以接受的解决方案。我认为这将取决于异常的类型,因为如果异常在任何级别都没有被挽救,那么这个场景仍然会失败。到目前为止,我主要使用这种方法来处理路由错误,它工作得很好。

#3


1  

It is possible to raise an exception in a When block and then make assertions about it in the following Then blocks.

可以在When块中引发异常,然后在接下来的then块中对其进行断言。

Using your example:

用你的例子:

When /^I do something unwanted$/ do
  @result = -> { throw_an_exception! }
end

Then /^an "(.*)" should be thrown$/ do |error|
  expect{ @result.call }.to raise_error(error)
end

That example uses RSpec's matchers but the important part is the -> (Lambda); which allows the reference to the throw_an_exception! method to be passed around.

这个例子使用了RSpec的匹配项,但重要的部分是-> (Lambda);它允许对throw_an_exception的引用!要传递的方法。

I hope that helps!

我希望会有帮助!

#4


-1  

I'm answering from the perspective of someone who uses Cucumber features in a Behavior-Driven Development situation, so take it or leave it...

我的回答是从一个在行为驱动的开发环境中使用黄瓜特征的人的角度出发的,所以要么接受,要么放弃……

Scenarios should be written to test a 'feature' or functionality of the application, as opposed to being used to test the code itself. An example being:

应该编写场景来测试应用程序的“特性”或功能,而不是用来测试代码本身。一个例子:

When the service is invoked
Then a success code should be returned

It sounds like your test case (i.e. If I do this, then this exception should be thrown) is a candidate for unit or integration testing - in my case, we would use some Mocking or unit testing framework.

听起来您的测试用例(例如,如果我这样做,那么应该抛出这个异常)是单元测试或集成测试的候选者——在我的例子中,我们将使用一些mock或单元测试框架。

My suggestion would be to re-evaluate your feature scenarios to see if they are really testing what you intend them to test. From personal experience, I've found that if my test classes are becoming abnormally complex, then my features are 'wrong.'

我的建议是重新评估您的特性场景,看看它们是否真的在测试您打算测试的特性。根据我的个人经验,我发现如果我的测试类变得异常复杂,那么我的特性就是“错误的”。

#1


5  

I thought about it once more, and maybe the answer is:

我又想了一遍,也许答案是:

There is no elegant solution, because the Given-When-Then-Scheme is violated in your case. You expect that "Then an exception should be thrown" is the outcome of "When I do something unwanted".

这里没有优雅的解决方案,因为在您的案例中,给定时-当时-当时-计划被违反了。你期望“那么应该抛出一个异常”是“当我做了不需要的事情”的结果。

But when you think about it, this is not true! The exception is not the outcome of this action, in fact the exception just shows that the "When"-Statement failed.

但是当你仔细想想,这不是真的!异常并不是该操作的结果,事实上,异常只是表明“When”-语句失败。

My solution to this would be to test at a higher level:

我的解决办法是在更高的水平上进行测试:

When I do something unwanted
Then an error should be logged

or

When I do something unwanted
Then the user should get an error message

or

When I do something unwanted
Then the program should be locked in state "error"

or a combination of these.

或者两者的结合。

Then you would "cache the exception" in your program - which makes perfect sense, as you most likely need to do that anyway.

然后你会在你的程序中“缓存这个异常”——这很有意义,因为无论如何你都很可能需要这么做。

The two problems you've stated would be solved, too.

你所说的两个问题也会得到解决。

In case you really must test for exceptions

如果您确实必须测试异常

Well, i guess then cucumber isn't the right test suite, hmm? ;-)

嗯,我猜黄瓜不是合适的测试套件,嗯?:-)

As the Given-When-Then-Scheme is violated anyway, I would simply write

无论如何,当“给予时”计划被违反时,我只会写下来

When I do something unwanted it should fail with "ArgumentError"

and in the step definitions something like (untested, please correct me if you try it)

在步骤定义中(未经测试,请纠正我,如果你尝试)

When /^I do something unwanted it should fail with "(.*)"$/ do |errorstring|
  expect {
    throw_an_exception!
  }.to raise_error(errorstring)
end

As said above, that is horribly wrong as the scheme is broken, but it would serve the purpose, wouldn't it? ;-)

如上所述,这是一个可怕的错误,因为这个计划被打破了,但它将服务于目的,不是吗?:-)

You'll find further documentation at testing errors at rspec expectations.

您将在rspec期望的测试错误中找到进一步的文档。

#2


4  

One option is to mark the scenario with @allow-rescue and check the page's output and status code. For example

一个选项是使用@allow-rescue标记该场景,并检查页面的输出和状态代码。例如

In my_steps.rb

在my_steps.rb

Then(/^the page (?:should have|has) content (.+)$/) do |content|
  expect(page).to have_content(content)
end

Then(/^the page should have status code (\d+)$/) do |status_code|
  expect(page.status_code.to_s).to eq(status_code)
end

Then /^I should see an error$/ do
  expect(400..599).to include(page.status_code)
end

In my_feature.feature

在my_feature.feature

@allow-rescue
Scenario: Make sure user can't do XYZ
  Given some prerequisite
  When I do something unwanted
  Then the page should have content Routing Error
  And the page should have status code 404

or alternatively:

或者:

@allow-rescue
Scenario: Make sure user can't do XYZ
  Given some prerequisite
  When I do something unwanted
  Then I should see an error

This may not be exactly what you were hoping for, but it might be an acceptable workaround for some people who come across this page. I think it will depend on the type of exception, since if the exception is not rescued at any level then the scenario will still fail. I have used this approach mostly for routing errors so far, which has worked fine.

这可能不是您所希望的,但是对于一些浏览此页面的人来说,这可能是一个可以接受的解决方案。我认为这将取决于异常的类型,因为如果异常在任何级别都没有被挽救,那么这个场景仍然会失败。到目前为止,我主要使用这种方法来处理路由错误,它工作得很好。

#3


1  

It is possible to raise an exception in a When block and then make assertions about it in the following Then blocks.

可以在When块中引发异常,然后在接下来的then块中对其进行断言。

Using your example:

用你的例子:

When /^I do something unwanted$/ do
  @result = -> { throw_an_exception! }
end

Then /^an "(.*)" should be thrown$/ do |error|
  expect{ @result.call }.to raise_error(error)
end

That example uses RSpec's matchers but the important part is the -> (Lambda); which allows the reference to the throw_an_exception! method to be passed around.

这个例子使用了RSpec的匹配项,但重要的部分是-> (Lambda);它允许对throw_an_exception的引用!要传递的方法。

I hope that helps!

我希望会有帮助!

#4


-1  

I'm answering from the perspective of someone who uses Cucumber features in a Behavior-Driven Development situation, so take it or leave it...

我的回答是从一个在行为驱动的开发环境中使用黄瓜特征的人的角度出发的,所以要么接受,要么放弃……

Scenarios should be written to test a 'feature' or functionality of the application, as opposed to being used to test the code itself. An example being:

应该编写场景来测试应用程序的“特性”或功能,而不是用来测试代码本身。一个例子:

When the service is invoked
Then a success code should be returned

It sounds like your test case (i.e. If I do this, then this exception should be thrown) is a candidate for unit or integration testing - in my case, we would use some Mocking or unit testing framework.

听起来您的测试用例(例如,如果我这样做,那么应该抛出这个异常)是单元测试或集成测试的候选者——在我的例子中,我们将使用一些mock或单元测试框架。

My suggestion would be to re-evaluate your feature scenarios to see if they are really testing what you intend them to test. From personal experience, I've found that if my test classes are becoming abnormally complex, then my features are 'wrong.'

我的建议是重新评估您的特性场景,看看它们是否真的在测试您打算测试的特性。根据我的个人经验,我发现如果我的测试类变得异常复杂,那么我的特性就是“错误的”。