Статьи

Тестирование сопоставлений NHibernate

В настоящее время я в процессе перемещения Suteki Shop из Linq-to-SQL в NHibernate . Это оказывается большая работа, чем я ожидал, и я скоро напишу пост о своем опыте. Я использую FNH ( Fluent NHibernate ) для создания файлов сопоставления, а не использую утомительное встроенное сопоставление XML. Одна из моих больших задач — написать модульные тесты для всех файлов сопоставления. Для каждого объекта я хочу иметь тест, который создает его экземпляр, сохраняет его в базе данных и извлекает его. Как вы можете себе представить, было бы утомительно написать все это. Имея это в виду, я создал простой тест NUnit, который можно повторно использовать для любого сопоставленного объекта:

using System;
using System.Reflection;
using NUnit.Framework;
using Suteki.Common.Extensions;
using Suteki.Shop.Tests.Models.Builders;

namespace Suteki.Shop.Tests.Maps
{
/// <summary>
/// Runs a simple test for each entity listed. That it can be saved to the database and retrieved.
/// </summary>
[TestFixture]
public class SimpleMapTests : MapTestBase
{
[TestCase(typeof (PostZone))]
[TestCase(typeof (Postage))]
[TestCase(typeof (Country))]
public void SimpleMapTest(Type entityType)
{
var runSimpleMapTest = GetType().GetMethod("RunSimpleMapTest");
var runSimpleMapTestForType = runSimpleMapTest.MakeGenericMethod(entityType);
runSimpleMapTestForType.Invoke(this, new object[0]);
}

public void RunSimpleMapTest<TEntity>() where TEntity : class, new()
{
var id = 0;

InSession(session =>
{
var entity = GetDefaultsFor<TEntity>();

session.Save(entity);
id = (int) entity.GetPrimaryKey().Value;
});

InSession(session => session.Get<TEntity>(id));
}

static TEntity GetDefaultsFor<TEntity>()
{
var entityName = typeof (TEntity).Name;
var defaults = typeof(Default).GetMethod(entityName, BindingFlags.Static | BindingFlags.Public);
if (defaults == null)
{
Assert.Fail(string.Format("No static method Default.{0} found", entityName));
}
if (defaults.ReturnType != typeof (TEntity))
{
Assert.Fail(string.Format("Method Default.{0} does not have a return type of {0}", entityName));
}

return (TEntity) defaults.Invoke(null, new object[0]);
}
}
}

Основная идея здесь заключается в том, чтобы использовать атрибут NUnit TestCase для запуска теста для каждой сущности, которую я хочу отобразить. У меня есть общий тестовый метод RunSimpleMapTest <TEntity>, который вызывается для каждой сущности с помощью SimpleMapTest. Существует немного отражения вуду для создания и запуска правильно напечатанного «RunSimpleMapTest», но принцип прост.

RunSimpleMapTest получает экземпляр класса сущности по умолчанию, сохраняет его, получает сгенерированный идентификатор и затем снова извлекает объект, используя идентификатор. Этого достаточно, чтобы проверить, что базовая карта работает и у меня нет не виртуальных членов в моей сущности. Метод «InSession» просто для того, чтобы сохранить некоторую типизацию, он выглядит так:

protected void InSession(Action<ISession> action)
{
using (var session = InMemoryDatabaseManager.OpenSession())
using(var transaction = session.BeginTransaction())
{
action(session);
transaction.Commit();
}
}

Метод GetPrimaryKey — это метод расширения, который находит первичный ключ любого объекта на основе простого соглашения; мои первичные ключи всегда называются «Id» или «<имя объекта> Id».

Я использую базу данных SQLite в памяти для тестов, управляемых базовым классом MapTestBase. Это настолько распространенное решение, что я оставлю его в Google, а не покажу свою реализацию здесь.

Последнее, на что нужно обратить внимание, это то, откуда происходит экземпляр сущности. Метод GetDefaultsFor <TEntity> ищет метод с тем же именем, что и у сущности в классе с именем «По умолчанию». Метод по умолчанию для PostZone выглядит следующим образом:

public static class Default
{
public static PostZone PostZone()
{
return new PostZone
{
Name = "UK",
Multiplier = 1.0M,
AskIfMaxWeight = false,
Position = 1,
IsActive = true,
FlatRate = 10M
};
}

.... lots of other similar methods
}

Теперь для каждой сущности я просто создаю ClassMap, создаю метод для создания сущности по умолчанию в классе Default и добавляю новый атрибут TestCase в мой тест.