随机生成测试数据是一种不好的做法吗? [英] Is it a bad practice to randomly-generate test data?

查看:74
本文介绍了随机生成测试数据是一种不好的做法吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

自从我开始使用rspec以来,我一直对夹具的概念有疑问.我主要担心的是:

Since I've started using rspec, I've had a problem with the notion of fixtures. My primary concerns are this:

  1. 我使用测试来揭示令人惊讶的行为.我并不总是很聪明,无法列举出我正在测试的示例的每种可能的情况.使用硬编码的固定装置似乎有局限性,因为它仅以我想象的非常特殊的情况测试我的代码. (不可否认,我的想象力也限制了我要测试的案例.)

  1. I use testing to reveal surprising behavior. I'm not always clever enough to enumerate every possible edge case for the examples I'm testing. Using hard-coded fixtures seems limiting because it only tests my code with the very specific cases that I've imagined. (Admittedly, my imagination is also limiting with respect to which cases I test.)

我使用测试作为代码文档的一种形式.如果我具有硬编码的夹具值,则很难揭示特定测试试图演示的内容.例如:

I use testing to as a form of documentation for the code. If I have hard-coded fixture values, it's hard to reveal what a particular test is trying to demonstrate. For example:

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.粗心的程序员可能会通过在before(:all)中创建Item或在fixtures/items.yml中插入另一个夹具来破坏测试.如果那是个大文件,可能要花很长时间才能找出问题所在.

Using the first method gives the reader no indication what the most expensive item is, only that its price is 100. All three methods ask the reader to take it on faith that the fixture :expensive is the most expensive one listed in fixtures/items.yml. A careless programmer could break tests by creating an Item in before(:all), or by inserting another fixture into fixtures/items.yml. If that is a large file, it could take a long time to figure out what the problem is.

我开始做的一件事是向所有模型添加#generate_random方法.仅当我运行规格时,此方法才可用.例如:

One thing I've started to do is add a #generate_random method to all of my models. This method is only available when I am running my specs. For example:

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

(执行此操作的具体细节实际上更简洁一些.我有一个处理所有模型的生成和清除的类,但是对于我的示例而言,此代码已经足够清楚了.)因此,在上面的示例中,我可能会进行如下测试.一个冒犯性的警告:我的代码严重依赖于before(:all):

(The specific details of how I do this are actually a bit cleaner. I have a class that handles the generation and cleanup of all models, but this code is clear enough for my example.) So in the above example, I might test as follows. A warning for the feint of heart: my code relies heavily on use of 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中的不确定性失败,可能会提示我出了点问题.如果我仅使用固定装置,发现这种错误可能会花费更长的时间.

This way, my tests better reveal surprising behavior. When I generate data this way, I occasionally stumble upon an edge case where my code does not behave as expected, but which I wouldn't have caught if I were only using fixtures. For example, in the case of #most_expensive, if I forgot to handle the special case where multiple items share the most expensive price, my test would occasionally fail at the first should. Seeing the non-deterministic failures in AutoSpec would clue me in that something was wrong. If I were only using fixtures, it might take much longer to discover such a bug.

我的测试在用代码演示预期行为方面也做得更好.我的测试清楚地表明,sorted是按价格降序排列的一组商品.由于我希望#most_expensive等于该数组的第一个元素,因此most_expensive的预期行为更加明显.

My tests also do a slightly better job of demonstrating in code what the expected behavior is. My test makes it clear that sorted is an array of items sorted in descending order by price. Since I expect #most_expensive to be equal to the first element of that array, it's even more obvious what the expected behavior of most_expensive is.

那么,这是不好的做法吗?我对灯具的恐惧是一种非理性的恐惧吗?为每个模型编写generate_random方法是否工作过多?还是行得通?

So, is this a bad practice? Is my fear of fixtures an irrational one? Is writing a generate_random method for each Model too much work? Or does this work?

推荐答案

这是对第二点的回答:

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

(2) I use testing to as a form of documentation for the code. If I have hard-coded fixture values, it's hard to reveal what a particular test is trying to demonstrate.

我同意.理想情况下,规范示例应该是可以自己理解的.使用固定装置是有问题的,因为它将示例的前提条件与预期结果分开.

I agree. Ideally spec examples should be understandable by themselves. Using fixtures is problematic, because it splits the pre-conditions of the example from its expected results.

因此,许多RSpec用户已经完全停止使用灯具.而是在spec示例本身中构造所需的对象.

Because of this, many RSpec users have stopped using fixtures altogether. Instead, construct the needed objects in the spec example itself.

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 机械师修复替换.

If your end up with lots of boilerplate code for object creation, you should take a look at some of the many test object factory libraries, such as factory_girl, Machinist, or FixtureReplacement.

这篇关于随机生成测试数据是一种不好的做法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆