Rails 3 + DataMapper - база данных не создается/не уничтожается между тестами

Я попробую здесь, так как список рассылки для DM, к сожалению, не имеет большого вклада от других пользователей.

Я достаточно уверен, что это не то, что мы должны делать вручную, но, возможно, я ошибаюсь. Я удалил ActiveRecord из своего проекта и начал создавать модели в DataMapper. Все работает, но я хочу написать юнит-тесты для своих моделей (и функционал для своих контроллеров). Однако моя тестовая база данных не очищается между тестовыми запусками (это легко проверить с помощью теста). AR позаботится об этом за вас, но похоже, что ребята из DM не учли это в своем проекте dm-rails.

В отчаянной попытке стереть все с лица земли я удалил все таблицы из своей тестовой базы данных. Теперь вместо того, чтобы мои модульные тесты терпели неудачу из-за грязной среды, они терпят неудачу из-за того, что схемы не существует. Глядя на доступные мне задачи rake, я не могу восстановить свою тестовую БД, не очистив мою базу данных разработки. Я начинаю сходить с ума и надеюсь, что другой пользователь DM + Rails 3 может подтолкнуть меня в правильном направлении.

В частности, когда я запускаю свои модульные тесты, все тестовые данные должны быть удалены между методами тестирования. Кроме того, если я внесу изменения в схему, я смогу запустить свои тесты, и они должны работать.

Я попытался поместить DataMapper.auto_migrate! в обратный вызов setup в моем test_helper.rb, но это, похоже, не создает схему (тесты по-прежнему терпят неудачу из-за того, что таблицы не существуют, когда они пытаются вставить/выбрать записи).

Я видел https://github.com/bmabey/database_cleaner, но действительно ли нам нужно приносить внешнюю библиотеку в Rails только для того, чтобы сделать что-то, для чего DM, вероятно, уже имеет (по-видимому, недокументированную) поддержку? Это также не решает проблему воссоздания схемы.


person d11wtq    schedule 10.05.2011    source источник


Ответы (1)


Ответ пришел в список рассылки, что это в основном ситуация «сделай сам», поэтому, чтобы избавить других от хлопот, если им тоже придется это делать:

Создайте файл .rake в lib/tasks, назовите что-то вроде test_db_setup.rake:

require File.dirname(__FILE__) + '/../../test/database_dumper'

# Custom logic that runs before the test suite begins
# This just clones the development database schema to the test database
# Note that each test does a lightweight teardown of just truncating all tables
namespace :db do

    namespace :test do
        desc "Reset the test database to match the development schema"
        task :prepare do
            Rake::Task['db:schema:clone'].invoke
        end
    end

    namespace :schema do
        desc "Literally dump the database schema into db/schema/**/*.sql"
        task :dump => :environment do
            DatabaseDumper.dump_schema(:directory => "#{Rails.root}/db/schema", :env => Rails.env)
        end

        desc "Clones the development schema into the test database"
        task :clone => [:dump, :environment] do
            DatabaseDumper.import_schema(:directory => "#{Rails.root}/db/schema", :env => "test")
        end
    end

end

task 'test:prepare' => 'db:test:prepare'

При этом используется хук :test:prepare, предоставляемый Rails, который запускается непосредственно перед началом набора тестов. Он копирует схему из вашей базы данных разработки в файлы .sql в каталоге db/schema/ (по одному на таблицу/представление), а затем импортирует эти файлы .sql в вашу тестовую базу данных.

Вам понадобится служебный класс, который я написал для этого (в настоящее время он написан для MySQL >= 5.0.1. Вам придется настроить логику, если вам нужна другая база данных.

# Utility class for dumping and importing the database schema
class DatabaseDumper
    def self.dump_schema(options = {})
        options[:directory] ||= "#{Rails.root}/db/schema"
        options[:env]       ||= Rails.env

        schema_dir = options[:directory]

        clean_sql_directory(schema_dir)

        Rails::DataMapper.configuration.repositories[options[:env]].each do |repository, config|
            repository_dir = "#{schema_dir}/#{repository}"
            adapter        = DataMapper.setup(repository, config)

            perform_schema_dump(adapter, repository_dir)
        end
    end

    def self.import_schema(options = {})
        options[:directory] ||= "#{Rails.root}/db/schema"
        options[:env]       ||= "test"

        schema_dir = options[:directory]

        Rails::DataMapper.configuration.repositories[options[:env]].each do |repository, config|
            repository_dir = "#{schema_dir}/#{repository}"
            adapter        = DataMapper.setup(repository, config)

            perform_schema_import(adapter, repository_dir)
        end
    end

    private

        def self.clean_sql_directory(path)
            Dir.mkdir(path) unless Dir.exists?(path)
            Dir.glob("#{path}/**/*.sql").each do |file|
                File.delete(file)
            end
        end

        def self.perform_schema_dump(adapter, path)
            Dir.mkdir(path) unless Dir.exists?(path)

            adapter.select("SHOW FULL TABLES").each do |row|
                name    = row.values.first
                type    = row.values.last
                sql_dir = "#{path}/#{directory_name_for_table_type(type)}"

                Dir.mkdir(sql_dir) unless Dir.exists?(sql_dir)

                schema_info = adapter.select("SHOW CREATE TABLE #{name}").first

                sql = schema_info.values.last

                f = File.open("#{sql_dir}/#{name}.sql", "w+")
                f << sql << "\n"
                f.close
            end
        end

        def self.directory_name_for_table_type(type)
            case type
                when "VIEW"
                    "views"
                when "BASE TABLE"
                    "tables"
                else
                    raise "Unknown table type #{type}"
            end
        end

        def self.perform_schema_import(adapter, path)
            tables_dir     = "#{path}/tables"
            views_dir      = "#{path}/views"

            { "TABLE" => tables_dir, "VIEW" => views_dir }.each do |type, sql_dir|
                Dir.glob("#{sql_dir}/*.sql").each do |file|
                    name       = File.basename(file, ".sql")
                    drop_sql   = "DROP #{type} IF EXISTS `#{name}`"
                    create_sql = File.open(file, "r").read

                    adapter.execute(drop_sql)
                    adapter.execute(create_sql)
                end
            end
        end
end

Это также оставит файлы .sql в вашем каталоге схемы, так что вы можете просмотреть их, если вам нужна ссылка.

Теперь это только сотрет вашу базу данных (путем установки новой схемы) при запуске набора тестов. Он не сотрет тесты между методами тестирования. Для этого вам нужно использовать DatabaseCleaner. Поместите это в свой test_helper.rb:

require 'database_cleaner'

DatabaseCleaner.strategy = :truncation, {:except => %w(auctionindexview helpindexview)}

class ActiveSupport::TestCase
    setup    :setup_database
    teardown :clean_database

    private

        def setup_database
            DatabaseCleaner.start
        end

        def clean_database
            DatabaseCleaner.clean
        end
end

Теперь вы должны быть готовы идти. Ваша схема будет обновлена, когда вы начнете выполнять тесты, у вас будет копия вашего SQL в каталоге db/schema, и ваши данные будут стерты между методами тестирования. Предупреждение, если вас соблазнила стратегия транзакций DatabaseCleaner... это редко бывает безопасной стратегией для использования в MySQL, поскольку ни один из типов таблиц MySQL в настоящее время не поддерживает вложенные транзакции, поэтому логика вашего приложения, скорее всего, нарушит разрыв . Truncate по-прежнему работает быстро и намного безопаснее.

person d11wtq    schedule 11.05.2011