【问题标题】:Rails: testing migration fileRails:测试迁移文件
【发布时间】:2016-05-20 03:43:09
【问题描述】:

我想测试我的迁移文件。有两个表:位置和用户。位置 has_many 用户。表通过 location_id 连接。迁移是向“位置”表中的“名称”列添加唯一约束,并删除“位置”表中具有重复位置名称的重复行,并使所有用户指向其位置的第一次出现。

这是我的迁移文件的一部分:

def self.remove_duplications
     grouped = all.group_by{|location| [location.name] }
     grouped.values.each do |duplicates|
       # the first one we want to keep right?
       first_one = duplicates.shift # or pop for last one
       users = User.all
       users.each do |user|
         if user.location && user.location.name == first_one.name
          user.location_id = first_one.id
          user.save!
         end
       end
       duplicates.each do |duplicate| 
         duplicate.destroy!
       end
     end
    end
  end

  def self.up
    Location.remove_duplications
    remove_index :locations, column: :name
    add_index :locations, :name, unique: true
  end

  def self.down
    remove_index :locations, column: :name # remove unique index
    add_index :locations, :name # adds just index, without unique
  end  

我该如何测试呢?手动测试它变得越来越困难。

【问题讨论】:

标签: ruby-on-rails unit-testing migration


【解决方案1】:

您应该首先将其拆分为两个不同的迁移:

class RemoveDuplicateLocations < ActiveRecord::Migration
  def up
    # @todo remove duplicate records ...
  end
end

# is not very complex now
class AddNameUniquenessToLocations < ActiveRecord::Migration
  def change
    remove_index :locations, column: :name
    add_index :locations, :name, unique: true
  end
end

这里的原因是它会使逻辑更容易,回滚也不会那么疯狂。此外,RemoveDuplicateLocations 迁移并不是真正可逆的 - 您无法在回滚中真正重新创建那些重复的位置记录(至少在不诉诸数据库备份的情况下不会)。

然后我们可以开始研究如何测试和实现RemoveDuplicateLocations。这是您如何测试它的粗略概述:

require 'test_helper'
require Rails.root.join('db', 'migrate', '20160519121310_remove_duplicate_locations.rb')

class RemoveDuplicateLocationsTest < MiniTest::Unit::TestCase

  let(:migration) { RemoveDuplicateLocations } 
  let(:previous_version) { "abc_123" # add previous state here } 

  # you may want to use factories or fixtures here
  let(:locations) do
    [
      Location.create(name: 'Paris')
      Location.create(name: 'Paris')
      Location.create(name: 'London')
    ]
  end
  let(:users) { locations.map.do { |l| User.create(location: l) } }

  def setup
    ActiveRecord::Migrator.migrate(['db/migrate'], previous)
    users # insert records by referencing lazy loading let var
  end

  def test_removes_duplicate_records
    migration.up
    duplicated_names = Location.group(:name)
                                 .having("count(name) > 1")
                                 .count.keys
    assert_empty duplicate_names
    assert_equals 2, Location.count
  end

  def test_relinks_user_location 
    migration.up
    users = User.where(location: { name: 'Paris' } )
    asset_equals 2, users.where(location: locations.first).count
    asset_equals 0, users.where.not(location: locations.first).count
  end
end

然后您将实施迁移:

class RemoveDuplicateLocations < ActiveRecord::Migration
  def up
    dupe_names = Location.group(:name).having("count(name) > 1").count.keys
    dupe_names.map do |name|
      dupes = Location.where(name: name).order(:id).ids
      first = dupes.shift
      User.where(location_id: dupes).update_all(location_id: first)
      Location.destroy_all(id: dupes)
    end
  end
end

这应该比您的尝试使用更少的内存,因为您没有将所有位置和用户都拉入内存 - 如果位置数量非常大,您可能仍然会遇到问题。

【讨论】:

  • 请注意,测试和迁移都不能保证运行!这是一个起点,而不是复制粘贴解决方案。
  • 在第一次migration.up之后我们必须向下迁移,我想这样才能运行第二次测试?如果我错了,请纠正我?
  • ActiveRecord::Migrator.migrate(previous) 应该在每次测试后重置状态。如果您使用的事务装置应该回滚创建的位置和用户记录(我认为,我使用工厂和 DatabaseCleaner),否则您应该在拆解中进行清理。
  • 您可能需要深入了解如何将数据库设置为正确的状态 - 我将首先了解 ActiveRecord::Migrator 的工作原理并查看 rails 测试github.com/rails/rails/blob/…
  • 非常感谢! :) 抓住了你的一些想法。我在一次迁移中做到了。虽然它有点凌乱,但它是一次迁移。测试也奏效了。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多