在概念上是否可以在测试方法中运行循环?
我想在控制器中测试一系列参数值,以确定不同的输入是否返回正确的值。
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
类似的东西。
答案 0 :(得分:6)
我通常会尝试避免测试代码中出现任何类型的条件语句或循环。您希望测试尽可能简单,如果您在测试中开始包含逻辑,则必须测试他们以确保它们按设计工作。我会将循环分解为单独的测试用例,这样如果其中任何一个失败,就更容易确切地确定导致失败的输入。当测试失败时,应该立即明白是什么导致了测试。您不必分析测试代码就可以搞清楚。
我确实想补充一点极其罕见的情况,你希望在你的测试用例中有一个循环。一个具体的例子是当你测试并发问题时。这是一般规则的一个例外,你应该有一个很好的理解,因为你的测试中有任何逻辑。
答案 1 :(得分:2)
在概念上是否可以在测试方法中运行循环?
你的意思是,这在政治上是正确的吗?
它有效,对吗?我想象一下反对意见是什么。
答案 2 :(得分:2)
测试也应该被视为你的软件应该做什么的“活文档”,所以尽可能保持清晰。
答案 3 :(得分:2)
在每个测试中使用多个断言或验证在政治上是不正确的。也就是说,每个人都这样做。在Cucumber的测试场景中可以看到其中一种新样式,其中场景仍然是一种极其可读的格式,但允许测试多组数据。
但是它是Ruby,如果你能跟随其他人对这封信的指导,就不会使用它。没有正确的方法,只有最常见的方式,并且经常发生变化。
我曾经问过我的牙医我应该刷牙,牙线和冲洗的顺序。他告诉我,他不在乎我是否真的做到了这三件事。我认为重点是,不合格的实施通常比没有实现更好。如果循环使测试更有趣,因此更有可能你应该从测试中循环出神圣的地狱。
答案 4 :(得分:1)
如果您可以让您的框架知道这个单一测试实际上正在执行多个测试(使用不同的参数),那就更好了。它允许您查看确切的参数组合失败以及哪些参数在测试报告中成功。
答案 5 :(得分:1)
在某些情况下,您可能需要循环,但您的其中一个不是必需的。请记住,为测试添加更多复杂性使得使用它们变得更加困难。当应用程序发展时,测试也会发展。如果你在开始时使它们过于复杂,那么有一天你可能会面临一个选择:
这是一个艰难的选择。在第一个选项中,您是否浪费时间实施新功能,而不是推动项目向前发展?你有时间吗?你的经理认为你有时间吗?客户在这个项目上花费的时间是否认为你有时间呢? 第二个选项接缝是合理的,但是,在编写新测试时,您如何知道您将所有案例都视为旧案例(加上新案例)?是文件中的全部内容吗?它是在测试文档中吗?你还记得他们吗?或者您可能会通过测试代码并重构它以揭示隐藏在此代码blob中的所有案例?这不是第一选择吗?
不要像传统代码那样进行测试。代码没有人想触摸,没有人真正知道,每个人都尽量避免和忽略它。测试应设计为休息应用程序。在应用于代码设计时应用许多设计原则。让它们变得简单。分担责任。按逻辑分组。让它们易于重构。使它们可扩展。你应该考虑很多事情。
至于你的情况。假设您有一个用例,其中您的代码为< 0,100>中的参数执行某些操作。 (代码中的0..5靠近在一起,当使用更宽的范围时,示例看起来更清晰)。在使用其他值时,它会执行一些异常处理。在这种情况下,您需要测试用例:
简单,独立的测试用例,易于重构,易于阅读,同时仍能正确检查代码 您可以添加测试用例,当参数在(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
个对象,那么“某事”测试无论如何都会失败,但是前面的测试非常清楚真正的问题是什么。
简而言之,避免它,但如果你有充分的理由去做,那就继续吧。如果它成为一个问题,那么测试仍然应该足够简单,将其重构为更少问题的东西很容易。