Случайная ошибка при тестировании с NHibernate на базе данных SQLite в памяти - PullRequest
8 голосов
/ 14 апреля 2011

У меня есть система, которая после получения сообщения ставит ее в очередь (записывает в таблицу), а другой процесс опрашивает БД и удаляет ее из очереди для обработки. В моих автоматических тестах я объединил операции в одном и том же процессе, но не могу (концептуально) объединить сеансы NH из двух операций.

Естественно - проблемы возникают.

Я прочитал все, что мог, чтобы заставить комбинацию SQLite-InMemory-NHibernate работать в мире тестирования, но теперь я столкнулся с СЛУЧАЙНО провальными тестами из-за ошибок "нет такой таблицы". Чтобы было понятно - «случайный» означает, что один и тот же тест с одинаковой точной конфигурацией и кодом иногда не удался.

У меня есть следующая конфигурация SQLite:

return SQLiteConfiguration
 .Standard
 .ConnectionString(x => x.Is("Data Source=:memory:; Version=3; New=True; Pooling=True; Max Pool Size=1;"))
 .Raw(NHibernate.Cfg.Environment.ReleaseConnections, "on_close");

В начале моего теста (каждого теста) я выбираю «статического» поставщика сеанса и прошу его очистить существующую БД и заново создать схему:

public void PurgeDatabaseOrCreateNew()
{
    using (var session = GetNewSession())
    using (var tx = session.BeginTransaction())
    {
            PurgeDatabaseOrCreateNew(session);
            tx.Commit();
    }
}

private void PurgeDatabaseOrCreateNew(ISession session)
{
    //http://ayende.com/Blog/archive/2009/04/28/nhibernate-unit-testing.aspx
    new SchemaExport(_Configuration)
        .Execute(false, true, false, session.Connection, null);
}

Так что да, он находится в другом сеансе, но соединение объединено в SQLite, поэтому следующий созданный мной сеанс увидит сгенерированную схему. Тем не менее, хотя в большинстве случаев это работает, иногда последующая операция «enqueue» завершается сбоем, потому что она не может видеть таблицу для моих входящих сообщений. Кроме того - кажется, что это происходит максимум один или два раза за запуск набора тестов; не все тесты проваливаются, только первый (а иногда и другой. Не совсем уверен, второй это или нет).

Худшая часть - это случайность, естественно. Я сказал себе, что исправил это несколько раз, просто потому, что он просто «перестал работать». Наугад.

Это происходит в FW4.0, версии System.Data.SQLite x86, Win7 64b и 2008R2 (всего три разных компьютера), NH2.1.2, настроенных с помощью FNH, в процессах TestDriven.NET 32b и процессах консоли NUnit 32b.

Помощь

Ответы [ 2 ]

8 голосов
/ 05 января 2012

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

Код пула SQLite кэширует соединение с помощью WeakReferences, что не является лучшим вариантом 1004 * для кэширования, поскольку ссылка на соединение (я) будет очищено, когда нет нормального (сильного) обращения к соединению, и ГХ запускается.Поскольку вы не можете предсказать, когда запустится GC, это объясняет «случайность».Попробуйте добавить GC.Collect(); между закрытием одного и открытием другого сеанса, ваш тест всегда будет неудачным.

Мое решение состояло в том, чтобы самостоятельно кэшировать соединение между открывающимися сеансами, например:

public class BaseIntegrationTest
{
    private static ISessionFactory _sessionFactory;
    private static Configuration _configuration;
    private static SchemaExport _schemaExport;

    // I cache the whole session because I don't want it and the
    // underlying connection to get closed.
    // The "Connection" property of the ISession is what we actually want.
    // Using the NHibernate SQLite Driver to get the connection would probably
    // work too.
    private static ISession _keepConnectionAlive;

    static BaseIntegrationTest()
    {
        _configuration = new Configuration();
        _configuration.Configure();
        _configuration.AddAssembly(typeof(Product).Assembly);
        _sessionFactory = _configuration.BuildSessionFactory();
        _schemaExport = new SchemaExport(_configuration);

        _keepConnectionAlive = _sessionFactory.OpenSession();
    }

    [SetUp]
    protected void RecreateDB()
    {
        _schemaExport.Execute(false, true, false, _keepConnectionAlive.Connection, null);
    }

    protected ISession OpenSession()
    {
        return _sessionFactory.OpenSession(_keepConnectionAlive.Connection);
    }
}

Каждый из моих интеграционных тестов наследуется от этого класса и вызывает OpenSession () для получения сеанса.RecreateDB вызывается NUnit перед каждым тестом из-за атрибута [SetUp].

Надеюсь, это поможет вам или любому другому, кто получит эту ошибку.

0 голосов
/ 21 декабря 2011

Единственное, что приходит в голову, что вы случайно оставляете сеанс открытым после теста.Вы должны убедиться, что любая существующая ISession закрыта, прежде чем открывать другую.Если вы не используете оператор using () или не вызываете Dispose () вручную, сеанс может быть где-то живым, вызывая эти случайные исключения.

...