3

可以(从概念上)在测试方法中运行 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

类似的东西。

4

8 回答 8

7

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

更新:

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

于 2009-11-24T12:58:44.240 回答
2

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

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

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

于 2009-11-24T12:58:14.037 回答
2

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

于 2009-11-24T13:26:14.963 回答
2

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

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

我曾经问过我的牙医,我应该按什么顺序刷牙、使用牙线和冲洗。他告诉我,他不在乎我是否真的做到了这三个方面。我相信重点是,通常不合标准的实现总比没有好。如果循环使测试变得更有趣,因此更有可能,那么你应该把地狱从你的测试中循环出来。

于 2009-11-24T16:16:08.740 回答
1

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

于 2009-11-24T13:00:43.393 回答
1

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

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

这是一个艰难的选择。在第一个选项中,您浪费时间为不会推动项目前进的事物实施新功能?你有时间吗?你的经理认为你有时间吗?客户是否认为你有时间在这个项目上支付你的时间?
第二个选项接缝是合理的,但是,在编写新测试时,你怎么知道你覆盖了所有旧的情况(加上新的)?这一切都在文档中吗?它在测试文档中吗?你还记得他们所有人吗?或者,您可能会检查测试代码并对其进行重构以揭示隐藏在此代码块中的所有案例?这不是成为首选吗?

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

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

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

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

于 2009-11-24T21:23:21.957 回答
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'
于 2010-01-11T19:37:53.137 回答
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

唯一一次我会以这种方式编写测试是如果集合中的项目彼此之间存在很大差异,但有一些需要验证共同的共享行为。例如,您可能有十个不同对象的实例,但它们都应该响应某些消息。所以你验证所有实例都可以做鸭子类型方法应该做的事情。但是,如果您有一个始终包含 的实例的集合,则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集合中有非对象,“某事”测试无论如何都会失败,但前面的测试非常清楚真正的问题是什么。

简而言之,避免它,但如果你有充分的理由这样做,那就继续吧。如果它在未来成为一个问题,测试应该仍然足够简单,以便将它重构为问题较少的东西是很容易的。

于 2009-11-24T21:55:34.133 回答