Статьи

Быстрые интеграционные тесты с NHibernate и SQLite

Одна вещь, которую я люблю использовать NHibernate, поскольку моя O / RM — это возможность выталкивать схему базы данных из домена. Это позволяет мне создавать базу данных с нуля для каждого интеграционного тестового устройства и переводить ее в требуемое состояние. Создать базу данных с помощью NHibernate быстро и просто. Черт, вот код для этого:

Configuration cfg = container.Resolve<Configuration>();  
SchemaExport export = new SchemaExport(cfg);
export.Execute(true, true, false, true);

Отличная, а?

Но тесты, которые попадают в базу данных, запоздалые , и они быстро становятся тестами, которые не запускаются Тесты, которые запрашивают базу данных, медленные, и, поскольку я регенерирую всю базу данных для каждого TestFixture, они действительно медленные. Я думаю, что одним из решений было бы не запускать интеграционные тесты локально, но мне не нравится этот вариант, и я считаю, что тесты следует запускать как можно чаще.

К счастью, NHibernate предоставляет ряд поставщиков RDBMS. Моим первым портом захода был SQLServer CE, но после первого запуска теста, когда он ничего не дал по формуле, я быстро отказался от нее.

Введите SQLite , «самый широко используемый в мире движок баз данных SQL». Потрясающие.

Быстрая модификация NHibernate и тесты были запущены так быстро, как свинья, чтобы …

Все было быстро и безболезненно. 

Во-первых: возьмите необходимые сборки с http://sqlite.phxsoftware.com/ и добавьте ссылку. 

Второе: настроить NHibernate; лично я использую возможности Castle NHibernate и Binsor для настройки Castle. Ниже приведены настройки, которые я использую:

facility NHibernateFacility:
configuration:
@isWeb = false, @useReflectionOptimizer = false
factory:
@id = 'nhibernate.factory'
settings(keymap, item: 'item'):
provider = 'NHibernate.Connection.DriverConnectionProvider'
connection.driver_class = 'NHibernate.Driver.SQLite20Driver'
dialect = 'NHibernate.Dialect.SQLiteDialect'
connection.connection_string = 'Data Source=intranet.db;Version=3;New=True'
cache.use_second_level_cache= 'false'
show_sql = 'true'
assemblies = [ Assembly.Load("Intranet") ]
facility TransactionFacility

И это в значительной степени это. Один известный мне недостаток — это SQLite на 64-битной ОС. Я не испытал это из первых рук, но я постараюсь выкопать некоторую информацию. Да, и на всю жизнь я не могу заставить его работать в памяти, но я опубликую об этом позже, как только получу решение .

Однако я отвлекся; В этой удивительной истории есть еще одна часть: сервер непрерывной интеграции . На мой взгляд, SQLite идеально подходит для локального запуска тестов, где важна скорость, а CI-сервер может успешно работать с SQL Server. Итак, давайте получим скрипт сборки, который меняет конфигурацию NHibernate. 

Для этого я использовал три файла бу:

  • container.boo
    Это мой основной файл бу, он устанавливает все мои классы в Windsor
  • sql_server_facilities.boo
    Это версия Sql Server версии файла средств, которая настраивает NHibernate для использования Sql Server.
  • sql_lite_facilites.boo
    Как и выше, но для SQLite

Container.boo выглядит примерно так:

import System.Web.Mvc from System.Web.Mvc
import Spark.Web.Mvc
import Spark
import Spark.FileSystem
import Invocas.Tools.Binsor.Macros from Invocas.Tools

import file from sql_lite_facilities.boo

LoadFacilities()

for type in AllTypesBased of IController("Invocas.Intranet"):
component type.FullName, type:
lifestyle Transient
log

В файл контейнера я включаю файл sql_lite_facilities.boo. Это, как вы уже не сомневаетесь, хранит конфигурацию объектов.

import System
import System.Reflection

import Castle.Facilities.AutomaticTransactionManagement
import Castle.Facilities.FactorySupport from Castle.MicroKernel
import Castle.Facilities.NHibernateIntegration from Castle.Facilities.NHibernateIntegration

def LoadFacilities():
LoadNHibernate()

def LoadNHibernate():
facility NHibernateFacility:
configuration:
@isWeb = false, @useReflectionOptimizer = false
factory:
@id = 'nhibernate.factory'
settings(keymap, item: 'item'):
provider = 'NHibernate.Connection.DriverConnectionProvider'
connection.driver_class = 'NHibernate.Driver.SQLite20Driver'
dialect = 'NHibernate.Dialect.SQLiteDialect'
connection.connection_string = 'Data Source=:memory:;Version=3;New=True'
cache.use_second_level_cache= 'false'
show_sql = 'true'
assemblies = [ Assembly.Load("Invocas.Intranet") ]
facility TransactionFacility

Sql_server_facilities.boo по сути тот же, но с правильно заданными провайдером, драйвером, диалектом и строкой соединения:

provider = 'NHibernate.Connection.DriverConnectionProvider'
connection.driver_class = 'NHibernate.Driver.SQLite20Driver'
dialect = 'NHibernate.Dialect.SQLiteDialect'
connection.connection_string = 'Data Source=:memory:;Version=3;New=True'

По умолчанию я обычно сохраняю этот файл, чтобы в нем был установлен файл Facilities.boo, установленный на SQLite, и использую NAnt, чтобы установить правильный файл. Ниже то, что нужно для файла NAnt:

<property name="common.booFileName" value="sql_lite_facilities" overwrite="true" readonly="true" />
<property name="common.defaultString" value="sql_lite_facilities" overwrite="true" readonly="true" />
....

<target name="modifyBoo">
<property name="booContainerFile" value="${build.dir}/container.boo" />
<property name="valueToFind" value="${common.defaultString}"/>
<property name="valueToReplace" value="${common.booFileName}"/>
<script language="C#">
<imports>
<import namespace="System.Text.RegularExpressions"/>
<import namespace="System.IO"/>
</imports>
<code>
<![CDATA[
public static void ScriptMain(Project project) {
StreamReader reader = File.OpenText(project.Properties["booContainerFile"]);
string file = String.Empty;
try {
Regex exp = new Regex(project.Properties["valueToFind"]);
file = reader.ReadToEnd();
file = exp.Replace(file, project.Properties["valueToReplace"]);
} finally {
reader.Close();
}

TextWriter tw = new StreamWriter(project.Properties["booContainerFile"]);
try {
tw.WriteLine(file);
} finally {
tw.Close();
}
}
]]>
</code>
</script>
</target>

Цель modifiyBoo должна быть вызвана до запуска модульных тестов, но я уверен, что вы это поняли. Чтобы предоставить новый файл бу, просто используйте следующее:

nant -D:common.booFileName=sql_server_facilities

И вот, у вас есть сверхбыстрые тесты интеграции SQLite локально и тесты SQL Server на CI-сервере. Zoom Zoom!