Страницы

среда, 1 февраля 2012 г.

Nhibernate. Тестируем быстро c SQLite.

  Кому приходилось писать интеграционные тесты с базой данных, тот познал трудности таких тестов. На то есть две основные причины.
Очистка базы данных для каждого теста.
  Пересоздание базы данных весьма долгое (по времени выполнения) и трудоёмкое занятие. С годами выработались основные подходы к решению этой проблемы:
  • Создавать базу из эталонной. 
  • Откатывать транзакцию по завершении тестов. 
  • Ломать/Создавать (Drop/Create) схему БД перед каждым тестом. 
  Полноценный анализ этих методов выходит за рамки этого поста, поэтому дам лишь свои краткие субъективные оценки.  Первый вариант самый медленный и сложный (БД долго поднимать из бекапа, и трудно поддерживать бекап в актуальном состоянии).  Второй быстрее, но мне он кажется некрасивым. Да еще возможно проблемы при распределённых транзакциях (привет интеграционному тестированию с использованием удалённых WCF-сервисов). Последний вариант самый простой и понятный (особенно для NHiberate с использованием SchemaExport). 

Скорость выполнения тестов. 
  С проблемой скорости дело обстоит сложнее. Межпроцессные вызовы всегда длятся на несколько порядков медленнее внутрипроцессных, сервера БД склонны записывать состояние БД в файл, что тоже отнимает время. И это серьёзные трудности на пути к эффективному тестированию работы с БД. К счастью, для пользователей NHibernate есть решение проблемы - SQLite  in-memory. Давайте попробуем. Вот пример простого теста с использование in-memory SQLite.
[Test] 
public void In_memory_fails_on_no_such_table()
{
    var config = new Configuration();

    config
        .ConfigureSQLiteInMemory()
        .MapEntities(From.ThisApplication());

    var factory = config.BuildSessionFactory();

    var schemaExport = new SchemaExport(config);

    schemaExport.Create(true, true);

    using(var session = factory.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        var product = new Product {Name = "Supercomputer"};
        session.Persist(product);
        tx.Commit();
    }
}

  Выглядит несложно, но... не работает. Падает с ошибкой "no such table HighLowGenerator". Хотя в скрипте есть dml-запрос на создание таблицы. Сначала вопрос куда делась "such table" может вводить в ступор. Дело в том, что схема БД (и все данные в ней) существует в памяти   пока открыто соединение. Как только соединение закрыто - всё, все данные и схема уничтожены. Это происходит в и нашем случае. SchemaExport честно создаёт схему БД в памяти, но после этого закрывает соединение. Сессия работает с новым открытым соединением, в котором нет ничего, даже таблиц. Вот и получаем no such table. Можно ли помочь беде!? Конечно! :)
  Сессия NHibernate не открывает и не закрывает внешние соединения, переданные ей извне (т.е. в метод ISessionFactory.OpenSession()). Достаточно лишь немного исправить код:
[Test] 
public void In_memory_succeeded()
{
    var config = new Configuration();

    config
        .ConfigurSQLiteInMemory()
        .MapEntities(From.ThisApplication());

    var factory = config.BuildSessionFactory();

    var connection = new SQLiteConnection("Data Source=:memory:;Version=3;New=True;");
    connection.Open();

    var schemaExport = new SchemaExport(config);

    schemaExport.Execute(true, true, false, connection, null);

    using(var session = factory.OpenSession(connection))
    using (var tx = session.BeginTransaction())
    {
        var product = new Product {Name = "Supercomputer"};
        session.Persist(product);
        session.Flush();
        tx.Commit();
    }
}
  Это работает, и быстро, практически мгновенно! Ниже в таблице сравнительный тест разных конфигураций SQLite (среднее за 100 прогонов на создание/удаление схемы БД для тестового проекта Enhima). "Анализ производительности" не претендует на точность, но представление о времени работы даёт.

Сравнительное время работы SQLite в разных конфигурациях.
In memory Virtual drive Real drive
Экспорт схемы, мс 2 19 1053
Полное время теста, мс 246 1929 105246
Способ очистки БД Закрытие соединения Удаление файла Удаление файла

  Конфигурация im-memory примерно на порядок быстрее, чем виртульный диск, и на три порядка быстрее настоящего диска. Так что у пользователей NHibernate нет повода не использовать SQLite для тестирования. :)
  Теоретические основы тестирования в связке NHibernate + SQLite я изложил. В следующий раз расскажу, как правильно организовать классы для тестирования с SQLite in-memory.

Комментариев нет:

Отправить комментарий