因为我已经开始使用最高反射率,我有一个问题与这一概念的具。我的主要关切问题是这个:

  1. 我用测试,以揭示令人惊讶的行为。我不总是足够聪明,列举每一个可能的边缘情况为例子,我的测试。使用硬编码似乎具限制性,因为它只是试验我的代码非常特殊的情况下,我想象的。(应当承认,我的想象力也限制相对于其情况下,我的试验。)

  2. 我用测试,以作为一种形式的文件编码。如果我有硬编码具的价值观,这是很难揭示了什么一个特别的测试,试图证明。例如:

    describe Item do
      describe '#most_expensive' do
        it 'should return the most expensive item' do
          Item.most_expensive.price.should == 100
          # OR
          #Item.most_expensive.price.should == Item.find(:expensive).price
          # OR
          #Item.most_expensive.id.should == Item.find(:expensive).id
        end
      end
    end
    

    使用第一种方法,让读者没有表明什么是最昂贵的项目是,只有它的价格是100。所有三个方法要求的读者采取它在信念的灯具 :expensive 是最昂贵的一所列 fixtures/items.yml.一个粗心的程序可能会破坏测试通过创建一个 Itembefore(:all), ,或通过插入另一具成 fixtures/items.yml.如果这是一个大的文件,它可能需要很长的时间才能弄清楚问题是什么。

有一件事我一开始要做的就是添加一个 #generate_random 方法来我所有的模型。这种方法仅适用当我运行了我的指示。例如:

class Item
  def self.generate_random(params={})
    Item.create(
      :name => params[:name] || String.generate_random,
      :price => params[:price] || rand(100)
    )
  end
end

(具体细节如何,我这样做实际上是一位吸尘器。我有一个类处理的产生和清理的所有的模型,但这种代码不够清楚我的例子。) 因此,在上述例子中,我可能会测试如下。一个警告佯攻之心我的代码在很大程度上依赖于使用 before(:all):

describe Item do
  describe '#most_expensive' do
    before(:all) do
      @items = []
      3.times { @items << Item.generate_random }
      @items << Item.generate_random({:price => 50})
    end

    it 'should return the most expensive item' do
      sorted = @items.sort { |a, b| b.price <=> a.price }
      expensive = Item.most_expensive
      expensive.should be(sorted[0])
      expensive.price.should >= 50      
    end
  end
end

这种方式,我的测试更好地揭示令人惊讶的行为。当我生成数据,这种方式,我偶尔会发现一种边缘情况下,我的代码不像预期的那样,但我不会被抓到如果我们仅使用固定装置。例如,在情况下 #most_expensive, 如果我忘了,以处理的特殊情况下多个项目,分享最昂贵的代价,我的测试有时会失败的第一个 should.看到非确定性中的失败AutoSpec会的线索我的东西是错误的。如果我们仅使用固定装置,它可能需要更长的时间,以发现这样一个错误。

我的测试中还做了一个稍微更好的工作表明在代码什么预期的行为。我的测试表明,排为一系列项目以降序排序的价格。因为我希望 #most_expensive 要等于第一个元件阵列,它甚至更明显的是什么期望的行为 most_expensive 是。

所以,这是一个不好的做法?是我的恐惧的固定装置的一个非理性的一个吗?是写一个 generate_random 方法为每个模型太多的工作?或者这是否工作?

有帮助吗?

解决方案

这是对你的第二点的回答:

  

(2)我使用测试作为代码的文档形式。如果我有硬编码的夹具值,很难揭示特定测试试图证明的内容。

我同意。理想情况下,规范示例本身应该是可以理解的。使用灯具是有问题的,因为它将示例的前提条件与其预期结果分开。

因此,许多RSpec用户已完全停止使用灯具。相反,在规范示例中构造所需的对象。

describe Item, "#most_expensive" do
  it 'should return the most expensive item' do
    items = [
      Item.create!(:price => 100),
      Item.create!(:price => 50)
    ]

    Item.most_expensive.price.should == 100
  end
end

如果最终有大量用于创建对象的样板代码,则应该查看一些测试对象工厂库,例如 factory_girl 机械师或< a href =“http://replacefixtures.rubyforge.org/”rel =“nofollow noreferrer”> FixtureReplacement 。

其他提示

我很惊讶在这个主题中没有人或 Jason Baker链接到提到 蒙特卡罗测试。这是我唯一一次广泛使用随机测试输入。但是,通过为每个测试用例提供随机数生成器的恒定种子,使测试可重现是非常重要的。

我们认为有关这个很多在最近的项目。在结束时,我们选定了两个要点:

  • 重复性的试验情况下是至关重要。如果你必须要写一个随机测试,以准备的文件,它广泛,因为如果失败的话,你会需要确切地知道为什么。
  • 使用随机性作为一个拐杖,用于代码复盖面意味着你要么没有良好的复盖范围,或者你不明白域的足够知道什么是代表性的测试案例。找出哪些是真正和解决它。

总之,随机性的,往往可以更多的麻烦比它的价值。仔细考虑是否你将要使用它的正确之前,你拉动扳机。我们最终决定,随机试验案件是一个坏主意在一般和谨慎使用,如果在所有。

已发布了大量有用的信息,但另请参阅:模糊测试。街上的一句话是,微软在很多项目中使用这种方法。

我的测试经验主要是用C / Python / Java编写的简单程序,所以我不确定这是否完全适用,但每当我有一个程序可以接受任何类型的用户输入时,我总是包括使用随机输入数据进行测试,或至少以不可预测的方式由计算机生成输入数据,因为您永远不能假设用户将输入什么。或者,你可以,但如果你这样做,那么一些不做出这种假设的黑客可能会发现你完全忽略的错误。机器生成的输入是我所知道的最好(仅限?)方式,可以将人类偏见完全排除在测试程序之外。当然,为了重现失败的测试,您必须执行诸如将测试输入保存到文件或在运行测试之前将其打印出来(如果是文本)。

只要您没有解决 oracle问题的解决方案,即确定哪个是您输入的软件的预期结果,随机测试是一种不好的做法。

如果你解决了oracle问题,你可以比简单的随机输入生成更进一步。您可以选择输入分布,这样您的软件的特定部分就可以通过简单随机的方式得到锻炼。

然后从随机测试切换到统计测试。

if (a > 0)
    // Do Foo
else (if b < 0)
    // Do Bar
else
    // Do Foobar

如果您在a范围内随机选择bint,则行使Foo 50%的时间,Bar 25%的时间和Foobar 25%的时间。您可能会在<=>中找到比<=>或<=>中更多的错误。

如果您选择<=>使其在66.66%的时间内为负,<=>和<=>比第一次分发更多地行使。实际上,这三个分支每33.33%的时间被运用。

当然,如果您观察到的结果与预期结果不同,则必须记录重现该错误的所有内容。

我建议看看机械师:

  

http://github.com/notahat/machinist/tree/master

Machinist将为您生成数据,但它是可重复的,因此每次测试运行都具有相同的随机数据。

你可以通过一致地播种随机数生成器来做类似的事情。

随机生成的测试用例的一个问题是验证答案应该通过代码来计算,你不能确定它没有错误:)

您可能还会看到以下主题:使用随机输入最佳做法进行测试

此类测试的有效性在很大程度上取决于您使用的随机数生成器的质量以及将RNG输出转换为测试数据的代码的正确性。

如果RNG从不产生导致代码进入某种边缘情况的值,则不会覆盖此案例。如果您将RNG输出转换为您测试的代码输入的代码有缺陷,即使使用良好的生成器,您仍然可能无法完成所有边缘情况。

你将如何测试?

测试用例中随机性的问题是输出是随机的。

测试背后的想法(特别是回归测试)是检查没有任何损坏。

如果您发现某些内容已损坏,则需要每次都包含该测试,否则您将无法获得一致的测试集。此外,如果您运行一个有效的随机测试,那么您需要包含该测试,因为您可能会破坏代码以使测试失败。

换句话说,如果你有一个使用随机生成的随机数据的测试,我认为这是一个坏主意。但是,如果您使用一组随机数据,那么您存储和重新使用,这可能是一个好主意。这可以采用随机数生成器的一组种子的形式。

通过存储生成的数据,您可以找到对此数据的“正确”响应。

因此,我建议使用随机数据来探索您的系统,但在测试中使用已定义的数据(最初可能是随机生成的数据)

使用随机测试数据是一种很好的做法 - 硬编码测试数据仅测试您明确考虑过的情况,而随机数据会清除您可能错误的隐含假设。

我强烈推荐使用Factory Girl和ffaker。 (在任何情况下都不要使用Rails装置。)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top