应该+ FactoryGirl:我可以让我的测试更快吗?

发布于 2024-08-09 19:38:07 字数 2248 浏览 11 评论 0原文

我正在寻找一种方法来加快我的 Shoulda + FactoryGirl 测试。

我尝试测试的模型 (StudentExam) 与其他模型有关联。在我创建 StudentExam 之前,这些关联对象必须存在。因此,它们是在setup 中创建的。

但是,我们的模型之一 (School) 需要大量时间来创建。因为 setup 在每个 should 语句之前被调用,所以整个测试用例需要花费很长时间才能执行——它创建了一个新的 @school@student@topic@exam 用于执行每个 should 语句。

我正在寻找一种方法来创建这些对象一次且仅一次。是否有类似 startup for before_all 方法的东西可以让我创建将在测试用例的其余部分中持续存在的记录?

基本上我正在寻找与 RSpec 的 before(:all) 完全相同的东西。我不担心依赖问题,因为这些测试永远不会修改那些昂贵的对象。

这是一个测试用例示例。对长代码表示歉意(我还创建了一个 gist):

# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase

  should_belong_to :student
  should_belong_to :exam

  setup do
    # These objects need to be created before we can create a StudentExam.  Tests will NOT modify these objects.
    # @school is a very time-expensive model to create (associations, external API calls, etc).
    # We need a way to create the @school *ONCE* -- there's no need to recreate it for every single test.
    @school = Factory(:school)
    @student = Factory(:student, :school => @school)
    @topic = Factory(:topic, :school => @school)
    @exam = Factory(:exam, :topic => @topic)
  end

  context "A StudentExam" do

    setup do
      @student_exam = Factory(:student_exam, :exam => @exam, :student => @student, :room_number => "WB 302")
    end

    should "take place at 'Some School'" do
      assert_equal @student_exam, 'Some School'
    end

    should "be in_progress? when created" do
      assert @student_exam.in_progress?
    end

    should "not be in_progress? when finish! is called" do
      @student_exam.finish!
      assert !@student_exam.in_progress
    end

  end

end

I'm looking for a way to speed up my Shoulda + FactoryGirl tests.

The model I'm trying to test (StudentExam) has associations to other models. These associated objects must exist before I can create a StudentExam. For that reason, they are created in setup.

However, one of our models (School) takes significant time to create. Because setup gets called before every should statement, the entire test case takes eons to execute -- it creates a new @school, @student, @topic and @exam for every should statement executed.

I'm looking for a way to create these objects once and only once. Is there something like a startup for before_all method that would allow me to create records which will persist throughout the rest of the test case?

Basically I'm looking for something exactly like RSpec's before(:all). I'm not concerned about the issue of dependencies since these tests will never modify those expensive objects.

Here's an example test case. Apologies for the long code (I've also created a gist):

# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase

  should_belong_to :student
  should_belong_to :exam

  setup do
    # These objects need to be created before we can create a StudentExam.  Tests will NOT modify these objects.
    # @school is a very time-expensive model to create (associations, external API calls, etc).
    # We need a way to create the @school *ONCE* -- there's no need to recreate it for every single test.
    @school = Factory(:school)
    @student = Factory(:student, :school => @school)
    @topic = Factory(:topic, :school => @school)
    @exam = Factory(:exam, :topic => @topic)
  end

  context "A StudentExam" do

    setup do
      @student_exam = Factory(:student_exam, :exam => @exam, :student => @student, :room_number => "WB 302")
    end

    should "take place at 'Some School'" do
      assert_equal @student_exam, 'Some School'
    end

    should "be in_progress? when created" do
      assert @student_exam.in_progress?
    end

    should "not be in_progress? when finish! is called" do
      @student_exam.finish!
      assert !@student_exam.in_progress
    end

  end

end

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

只是我以为 2024-08-16 19:38:07

如果问题是仅创建这些记录一次,则可以使用类变量。
这不是一个干净的方法,但至少应该有效。

# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase

  should_belong_to :student
  should_belong_to :exam

  # These objects need to be created before we can create a StudentExam.  Tests will NOT modify these objects.
  # @school is a very time-expensive model to create (associations, external API calls, etc).
  # We need a way to create the @school *ONCE* -- there's no need to recreate it for every single test.
  @@school = Factory(:school)
  @@student = Factory(:student, :school => @@school)
  @@topic = Factory(:topic, :school => @@school)
  @@exam = Factory(:exam, :topic => @@topic)


  context "A StudentExam" do

    setup do
      @student_exam = Factory(:student_exam, :exam => @@exam, :student => @@student, :room_number => "WB 302")
    end

    should "take place at 'Some School'" do
      assert_equal @student_exam, 'Some School'
    end

    should "be in_progress? when created" do
      assert @student_exam.in_progress?
    end

    should "not be in_progress? when finish! is called" do
      @@student_exam.finish!
      assert !@student_exam.in_progress
    end

  end

end

编辑:要修复超级丑陋的解决方法,请使用实例方法推迟评估。

# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase

  ...

  private

    def school
      @@school ||= Factory(:school)
    end

    # use school instead of @@school
    def student
      @@school ||= Factory(:student, :school => school)
    end

end

If the problem is creating these records only once, you can use a class variable.
It's not a clean approach but at least it should work.

# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase

  should_belong_to :student
  should_belong_to :exam

  # These objects need to be created before we can create a StudentExam.  Tests will NOT modify these objects.
  # @school is a very time-expensive model to create (associations, external API calls, etc).
  # We need a way to create the @school *ONCE* -- there's no need to recreate it for every single test.
  @@school = Factory(:school)
  @@student = Factory(:student, :school => @@school)
  @@topic = Factory(:topic, :school => @@school)
  @@exam = Factory(:exam, :topic => @@topic)


  context "A StudentExam" do

    setup do
      @student_exam = Factory(:student_exam, :exam => @@exam, :student => @@student, :room_number => "WB 302")
    end

    should "take place at 'Some School'" do
      assert_equal @student_exam, 'Some School'
    end

    should "be in_progress? when created" do
      assert @student_exam.in_progress?
    end

    should "not be in_progress? when finish! is called" do
      @@student_exam.finish!
      assert !@student_exam.in_progress
    end

  end

end

EDIT: To fix the super-ugly workaround postpone the evaluation with an instance method.

# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase

  ...

  private

    def school
      @@school ||= Factory(:school)
    end

    # use school instead of @@school
    def student
      @@school ||= Factory(:student, :school => school)
    end

end
鲜血染红嫁衣 2024-08-16 19:38:07

您想编写什么样的测试?如果您确实想确保所有这些对象都适当协调,那么您正在编写集成测试,速度不是您主要关心的问题。但是,如果您尝试对模型进行单元测试,则可以通过积极进行存根来获得更好的结果。

例如,如果您在调用 exam.location(或任何您所调用的名称)时尝试检查考试是否使用其学校协会的名称,则不需要整个 school 对象。您只需要确保考试在学校调用正确的方法即可。为了测试这一点,您可以执行如下操作(使用 Test::Unit 和 Mocha,因为这是我熟悉的):

test "exam gets location from school name" do
  school = stub_everything
  school.expects(:name).returns(:a_school_name)
  exam = Factory(:exam, :school => school)

  assert_equal :a_school_name, exam.location
end

基本上,如果您需要加快单元测试速度,因为对象的构建成本太高,您' re 并不是真正的单元测试。上面所有的测试用例感觉都应该处于单元测试级别,所以存根存根!

What kind of tests are you trying to write? If you actually want to make sure that all of these objects are coordinating appropriately, you're writing an integration test and speed is not your primary concern. However, if you're trying to unit test the model, you could achieve better results by stubbing aggressively.

For example, if you're trying to check that an exam uses the name of its school association when you call exam.location (or whatever you're calling it), you don't need a whole school object. You just need to make sure that exam is calling the right method on school. To test that, you could do something like the following (using Test::Unit and Mocha because that's what I'm familiar with):

test "exam gets location from school name" do
  school = stub_everything
  school.expects(:name).returns(:a_school_name)
  exam = Factory(:exam, :school => school)

  assert_equal :a_school_name, exam.location
end

Basically, if you need to speed up your unit tests because objects are too expensive to construct, you're not really unit testing. All of the test cases above feel like they should be at the unit test level, so stub stub stub!

兔姬 2024-08-16 19:38:07

http://m.onkey. org/2009/9/20/make-your-shoulda-tests-faster-with-fast_context 是一篇关于如何使用名为 fast_context 的 gem 使您的 Shoulda/factory-girl 测试更快的优秀文章。如果这不是您需要的,请告诉我。

http://m.onkey.org/2009/9/20/make-your-shoulda-tests-faster-with-fast_context is an excellent post about how to make your shoulda/factory-girl tests faster, using a gem called fast_context. Let me know if its not what you need.

饮湿 2024-08-16 19:38:07

有一个名为 fast_context(github 链接)将 should 语句组合到单个上下文中,从而加快测试速度。

我用来加速测试的另一件事是预先填充夹具数据。 FactoryGirl 速度很慢,因为每次安装块运行时它都会创建这些记录。

我编写了一个名为 Fixie 的插件,它使用 ActiveRecord 预先填充测试数据库,以便您需要的记录因为您的测试已经创建。如果您也想在运行时创建新记录,则可以将 Fixie 与 FactoryGirl 一起使用。

There is a plugin called fast_context (github link) that combines should statements into a single context, speeding up the tests.

The other thing I have been using to speed up my tests is pre-populating the fixture data. FactoryGirl is slow because it's creating those records each time the setup block runs.

I wrote a plugin called Fixie that uses ActiveRecord to pre-populate the test database, so the records you need for your tests are already created. You can use Fixie along with FactoryGirl if you want to create new records at run-time, too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文