在功能测试方法中运行 for 循环可以吗?

发布于 2024-08-12 09:59:28 字数 588 浏览 6 评论 0原文

在测试方法中运行 for 循环可以(概念上)吗?

我想测试控制器中的一系列参数值,以确定不同的输入是否返回正确的值。

  test "logged in user - add something - 0 qty" do
    @app = Factory.create(:app)

    (0..5).each do |t|
      @qty = t
      login(:user)
      get :add. :id => @app.id, :qty => @qty
      assert_nil(flash[:error])
      assert_response :redirect
      assert_redirect_to :controller => :apps, :action => :show, :id => @app.id
      if @qty = 0 
        assert_equal(Invite.all.count, @qty + 1)
      else 
        assert_something .........
    end
  end

类似的事情。

Is it okay (conceptually) to run for loops in test methods?

I'd like to test a range of parameter values into a controller, to determine if the different inputs return the correct values.

  test "logged in user - add something - 0 qty" do
    @app = Factory.create(:app)

    (0..5).each do |t|
      @qty = t
      login(:user)
      get :add. :id => @app.id, :qty => @qty
      assert_nil(flash[:error])
      assert_response :redirect
      assert_redirect_to :controller => :apps, :action => :show, :id => @app.id
      if @qty = 0 
        assert_equal(Invite.all.count, @qty + 1)
      else 
        assert_something .........
    end
  end

Something like that.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

谁的新欢旧爱 2024-08-19 09:59:29

在测试方法中运行 for 循环可以(概念上)吗?

你的意思是,政治正确吗?

它有效,对吧?我试着想象反对意见会是什么。

Is it okay (conceptually) to run for loops in test methods?

You mean, is it politically correct?

It works, right? I'm trying to imagine what the objection would be.

路弥 2024-08-19 09:59:29

测试还应该被视为您的软件应该做什么的“活文档”,因此请尽可能使其清晰。

Tests should also be viewed as "living documentation" of what your software is supposed to do, so keep them as clear as possible.

不醒的梦 2024-08-19 09:59:29

在每个测试中使用多个断言或验证在政治上是不正确的。也就是说,每个人都这样做。其中一种新样式出现在 Cucumber 的测试场景中,该场景仍然采用极其可读的格式,但允许测试多组数据。

但它是 Ruby,如果您是那种严格遵循其他人指导的人,您就不会使用它。没有正确的方法,只有最常见且经常变化的方法。

我曾经问我的牙医我应该按哪个顺序刷牙、用牙线和漱口。他告诉我,他不在乎我是否真的做到了这三件事。我认为重点在于,不合格的实施往往比没有实施要好。如果循环使测试变得更有趣,因此更有可能,那么您应该从测试中循环出神圣的地狱。

It is not politically correct to use more than one assertion or validation in each test. That said, everyone does it. One of the new styles is seen in Cucumber's test scenarios where the scenario is still in an extremely readable format but allows multiple sets of data to be tested.

But it's Ruby, you wouldn't be using it if you were the sort to follow everyone else's guidance to the letter. There is no right way, only the most common and that changes quite often.

I once asked my dentist which order I should brush, floss, and rinse. He told me that he didn't care if I managed to actually do all three. I believe the point was that oftentimes substandard implementations are better than none at all. If loops make testing more fun and therefore more likely then you should loop the holy hell out of your tests.

审判长 2024-08-19 09:59:29

如果您可以让您的框架意识到这一单个测试实际上正在执行多个测试(使用不同的参数),那就更好了。它允许您在测试报告中查看哪些参数组合失败,哪些参数组合成功。

It is even better if you can have your framework be aware that this one single test is actually performing multiple tests (with different parameters). It allows you to see what exact parameter combination fails and which succeed in the test-report.

眼角的笑意。 2024-08-19 09:59:29

在某些情况下,您可能需要循环,但您的循环并不是其中之一。请记住,增加测试的复杂性会使测试变得更加困难。当应用程序发展时,测试也会发展。如果你一开始就让它们太复杂,那么有一天你可能会面临一个选择:

  • 我应该花 3 天来重构这个失败的旧的、混乱的测试吗?
  • 应该删除测试并编写新的、更简单优雅的(3,5天)?

这是一个艰难的选择。在第一个选项中,您浪费时间来实现不会推动项目前进的新功能吗?你有时间做这个吗?你的经理认为你有时间做这件事吗?为您在该项目上花费时间的客户认为您有时间吗?
第二个选项似乎是合理的,但是,在编写新测试时,您如何知道您涵盖了旧案例(加上新案例)的所有案例?一切都在文档中吗?测试文档中有吗?你都记得吗?或者,您可能会检查测试代码并重构它以揭示隐藏在该代码块中的所有情况?这不是成为首选吗?

不要像遗留代码那样进行测试。没有人愿意接触的代码,没有人真正知道,每个人都试图尽可能避免和忽略它。测试应设计为休息应用程序。在应用于代码设计时应用许多设计原则。让它们变得简单。责任分开。将它们按逻辑分组。使它们易于重构。使它们可扩展。您应该考虑很多事情。

至于你的情况。让我们假设您的代码对 <0,100> 中的参数执行某些操作。 (代码中的 0..5 很接近,使用更宽的范围时示例看起来更清晰)。对于其他值,它会进行一些异常处理。在这种情况下,您需要测试用例:

  • 当参数 = -1
  • 当参数 = 0
  • 当参数 = 1
  • 当参数 = 99
  • 当参数 = 100
  • 当参数 = 101

简单、独立的测试用例,易于重构,易于阅读,同时仍可检查正确编码。
您可以添加测试用例,在其中使用循环来检查参数位于 (10,70) 时的行为,但不建议这样做。大量的测试和广泛的参数范围只是浪费资源。如果算法是确定性的,则对一组值执行相同的步骤,如果它适用于一个值,那么它将适用于所有这些值。
尝试阅读有关等价类、边界值、成对测试、路径覆盖、语句覆盖、分支覆盖和其他测试技术的知识,以使您的测试更好。

There may be cases where you need loops but yours is not necessary one of them. Remember that adding more complexity to tests makes harder to work with them. When application evolves tests evolve too. If you make them too complex at start, than one day you may face a choice:

  • should I spend 3 days to refactor this big old cluttered test that fails?
  • should delete the test and write new, simpler elegant (in 3,5 day)?

This is hard choice. In first option you waste time for implementing new features for something that doesn't push project forward? Do you have time for this? Does your manager think you have time for this? Does client paying for your time on this project think that you have time for this?
Second option seams to be reasonable but, when writing new tests how do you know that you covered all cases as old one (plus new ones)? Is it all in documentation? Is it in test documentation? Do you remember all of them? Or maybe you go through test code and refactor it to reveal all cases hidden inside this code blob? Isn't this becoming first option?

Don't make tests like legacy code. Code no one wants to touch, no one really knows, everyone trying to avoid and ignore it as much as possible. Tests should be designed as rest application. Apply many design principles as you are applying to code design. Make them simple. Separate responsibility. Group them logically. Make them easy to refactor. Make them extensible. There are many things you should take into consideration.

As for your case. Let's Assume that you have use case where your code does something for parameter in <0,100> (0..5 from your code is close together, example looks more clear when wider range is used). In with other values it does some exception handling. In this case you want test cases:

  • when parameter = -1
  • when parameter = 0
  • when parameter = 1
  • when parameter = 99
  • when parameter = 100
  • when parameter = 101

Simple, separate test cases that are easy to refactor, easy to read while still checking code properly.
You could add test case where you would use loop to check behavior when parameter is in (10,70) but it is not recommended. With lots of tests and wide parameters ranges it is just waste of resources. If algorithm is deterministic, does the same steps for some set of values it will work for all of them if it works for one.
Try to read about equivalence classes, boundary values, pairwise testing, path coverage, statement coverage, branch coverage and other testing techniques to make your tests better.

花期渐远 2024-08-19 09:59:29

我要把自己加入到政治不正确的名单中。当测试一系列值时,循环可以提高测试的可读性。此外,它有助于 DRY,使重构更容易:您愿意将新参数添加到测试中调用该方法的八个位置,还是只添加一个?

这是使用此技术的测试。它使用自行开发的测试库,但该技术是通用的:

  def test_swap_name
    test_cases = [
      [
        'Paul, Ron P.A.',
        'Ron Paul PA'
      ],
      [
        "PUBLIC, SR., JOHN Q",
        "JOHN Q PUBLIC SR"
      ],
      [
        "SMITH, JR., MARK A",
        "MARK A SMITH JR"
      ],
      [
        'James Brown',
        'James Brown'
      ],
      # (more test cases)
    ]
    for original, swapped in test_cases
      assertInfo("For original = #{original.inspect}") do
        assertEquals(original.swap_name, swapped)
      end
    end
  end

assertInfo 将任意字符串添加到任何异常消息的开头。这就是您如何知道当测试失败时正在测试哪些数据:

./StringUtil.test.rb
Method "test_swap_name" failed:
Assert::BlownAssert: For original = "Paul, Ron P.A.": Expected:
"Ron Paul PA"
but got
"Paul, Ron P.A."
./../../testlib/Assert.rb:125:in `fail_test'
./../../testlib/Assert.rb:43:in `assertEquals'
./StringUtil.test.rb:627:in `test_swap_name'

I'm going to add myself to the list of the politically incorrect. When testing a range of values, a loop can increase the readability of a test. Furthermore, it aids DRY, making refactoring easier: would you rather add the new parameter to the eight places the method is called in the test, or to just one?

Here's a test that uses this technique. It's using a home-grown test library, but the technique is universal:

  def test_swap_name
    test_cases = [
      [
        'Paul, Ron P.A.',
        'Ron Paul PA'
      ],
      [
        "PUBLIC, SR., JOHN Q",
        "JOHN Q PUBLIC SR"
      ],
      [
        "SMITH, JR., MARK A",
        "MARK A SMITH JR"
      ],
      [
        'James Brown',
        'James Brown'
      ],
      # (more test cases)
    ]
    for original, swapped in test_cases
      assertInfo("For original = #{original.inspect}") do
        assertEquals(original.swap_name, swapped)
      end
    end
  end

assertInfo adds an arbitrary string to the beginning of any exception message. That's how you can know, when a test failed, which data was being tested:

./StringUtil.test.rb
Method "test_swap_name" failed:
Assert::BlownAssert: For original = "Paul, Ron P.A.": Expected:
"Ron Paul PA"
but got
"Paul, Ron P.A."
./../../testlib/Assert.rb:125:in `fail_test'
./../../testlib/Assert.rb:43:in `assertEquals'
./StringUtil.test.rb:627:in `test_swap_name'
随风而去 2024-08-19 09:59:29

只要测试中只出现一次断言,我通常就可以接受测试中的循环。换句话说,这是可以的:

test "something" do
  for item in @collection
    assert_something item
  end
end

但这不是:

test "something" do
  for item in @collection
    assert_something item
    assert_something_else item
  end
end

这些会让你讨厌自己:

test "something" do
  for item in @collection
    assert_something item
    if x == y
      assert_something_else item
    end
  end
end

test "something" do
  for item in @collection
    assert_something item
  end
  for item in @collection
    assert_something_else item
  end
end

我唯一一次以这种方式编写测试是如果集合中的项目彼此之间存在很大差异,但有一些需要验证共同的行为。例如,您可能有十个不同对象的实例,但它们都应该响应某些消息。因此,您验证所有实例都可以执行鸭子类型方法应该执行的操作。但是,如果您有一个始终包含 Foo 实例的集合,则通常最好只对 @collection.first 进行断言。测试执行得更快,并且在所有实例上重复断言并没有真正获得太多好处,并且在大多数情况下,最好只测试 item 与集合的其余部分隔离反正。如果您感觉特别偏执,这通常是可以的:

test "items are all Foo" do
  for item in @collection
    assert_kind_of Foo, item, "Everything in @collection must be Foo."
  end
end
test "something" do
  assert_something @collection.first
end

如果集合中有非 Foo 对象,“something”测试无论如何都会失败,但是前面的测试已经非常清楚地表明了什么真正的问题是。

简而言之,避免它,但如果你有充分的理由这样做,那就继续吧。如果它在以后成为问题,测试仍然应该足够简单,以便可以轻松地将其重构为问题较少的东西。

I'm generally OK with a loop in a test as long as there's only one occurrence of an assertion in the test. In other words, this is OK:

test "something" do
  for item in @collection
    assert_something item
  end
end

But this is not:

test "something" do
  for item in @collection
    assert_something item
    assert_something_else item
  end
end

And these will make you hate yourself:

test "something" do
  for item in @collection
    assert_something item
    if x == y
      assert_something_else item
    end
  end
end

test "something" do
  for item in @collection
    assert_something item
  end
  for item in @collection
    assert_something_else item
  end
end

And the only time I would write a test this way is if the items in the collection vary from each other substantially, but there was some need to verify commonly shared behavior. For instance, you might have ten instances of different objects, but they all are supposed to respond to some message. So you verify that all instances can do the thing that the duck-typed method is supposed to do. But if you have a collection that always contains instances of Foo, you're often better off just making an assertion about @collection.first. The test executes faster, and you don't really gain much from repeating the assertion on all instances, and in most cases, you're much better off just testing item in isolation from the rest of the collection anyway. If you're feeling particularly paranoid, this is generally OK:

test "items are all Foo" do
  for item in @collection
    assert_kind_of Foo, item, "Everything in @collection must be Foo."
  end
end
test "something" do
  assert_something @collection.first
end

The "something" test will fail anyways if you've got non-Foo objects in the collection, but the preceding test makes it abundantly clear what the real problem is.

In a nutshell, avoid it, but if you've got a good reason to do it, then go ahead. And if it becomes a problem down the road, the test should still be simple enough that refactoring it into something less problematic is easy.

夏了南城 2024-08-19 09:59:28

我通常尝试避免测试代码中任何类型的条件语句或循环。您希望测试尽可能简单,如果您开始在测试中包含逻辑,则必须测试它们以确保它们按设计工作。我会将循环分解为单独的测试用例,这样如果其中任何一个失败,就可以更轻松地准确找出导致失败的输入。当测试失败时,导致失败的原因应该立即显而易见。您不必分析测试代码来找出答案。

更新:

我确实想补充一点,在一些极其罕见的情况下,您会希望在测试用例中使用循环。一个具体的例子是当您测试并发问题时。这是一般规则的一个例外,您应该有一个非常充分且易于理解的理由来在测试中使用任何类型的逻辑。

I usually try to avoid any kind of conditional statements or loops in test code. You want your tests to be as simple as possible, and if you start including logic in your tests you have to test them to make sure they work as designed. I would break the loop up into separate test cases, that way if any one of them fails it's easier to pinpoint exactly what inputs caused the failure. When a test fails it should be immediately obvious what caused it. You shouldn't have to analyze the test code to figure it out.

Update:

I do want to add that there are some extremely rare cases where you would want to have a loop in your test cases. One specific example is when you're testing for concurrency issues. This is an exception to the general rule, and you should have a very good and well-understood reason for having any kind of logic in your tests.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文