在功能测试方法中运行循环是否可以?

时间:2009-11-24 12:39:59

标签: ruby-on-rails ruby unit-testing functional-testing

在概念上是否可以在测试方法中运行循环?

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

  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

类似的东西。

8 个答案:

答案 0 :(得分:6)

我通常会尝试避免测试代码中出现任何类型的条件语句或循环。您希望测试尽可能简单,如果您在测试中开始包含逻辑,则必须测试他们以确保它们按设计工作。我会将循环分解为单独的测试用例,这样如果其中任何一个失败,就更容易确切地确定导致失败的输入。当测试失败时,应该立即明白是什么导致了测试。您不必分析测试代码就可以搞清楚。

更新

我确实想补充一点极其罕见的情况,你希望在你的测试用例中有一个循环。一个具体的例子是当你测试并发问题时。这是一般规则的一个例外,你应该有一个很好的理解,因为你的测试中有任何逻辑。

答案 1 :(得分:2)

  

在概念上是否可以在测试方法中运行循环?

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

它有效,对吗?我想象一下反对意见是什么。

答案 2 :(得分:2)

测试也应该被视为你的软件应该做什么的“活文档”,所以尽可能保持清晰。

答案 3 :(得分:2)

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

但是它是Ruby,如果你能跟随其他人对这封信的指导,就不会使用它。没有正确的方法,只有最常见的方式,并且经常发生变化。

我曾经问过我的牙医我应该刷牙,牙线和冲洗的顺序。他告诉我,他不在乎我是否真的做到了这三件事。我认为重点是,不合格的实施通常比没有实现更好。如果循环使测试更有趣,因此更有可能你应该从测试中循环出神圣的地狱。

答案 4 :(得分:1)

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

答案 5 :(得分:1)

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

  • 我应该花3天的时间来重构那个失败的旧的混乱测试吗?
  • 应该删除测试并写出新的,更简单的优雅(在3,5天内)?

这是一个艰难的选择。在第一个选项中,您是否浪费时间实施新功能,而不是推动项目向前发展?你有时间吗?你的经理认为你有时间吗?客户在这个项目上花费的时间是否认为你有时间呢? 第二个选项接缝是合理的,但是,在编写新测试时,您如何知道您将所有案例都视为旧案例(加上新案例)?是文件中的全部内容吗?它是在测试文档中吗?你还记得他们吗?或者您可能会通过测试代码并重构它以揭示隐藏在此代码blob中的所有案例?这不是第一选择吗?

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

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

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

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

答案 6 :(得分:1)

我要将自己添加到政治上不正确的列表中。在测试一系列值时,循环可以提高测试的可读性。此外,它有助于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'

答案 7 :(得分:0)

只要测试中只出现一次断言,我通常可以在测试中使用循环。换句话说,这没关系:

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

我唯一一次以这种方式编写测试的方法是,集合中的项目是否大相径庭,但有些需要验证共同的行为。例如,您可能有10个不同对象的实例,但它们都应该响应某些消息。因此,您验证所有实例都可以执行duck-typed方法应该执行的操作。但是如果你有一个总是包含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个对象,那么“某事”测试无论如何都会失败,但是前面的测试非常清楚真正的问题是什么。

简而言之,避免它,但如果你有充分的理由去做,那就继续吧。如果它成为一个问题,那么测试仍然应该足够简单,将其重构为更少问题的东西很容易。