使用 MbUnit3 的 [Rollback] 對 NHibernate 與 SQLite 的交互進行單元測試 (Using MbUnit3's [Rollback] for unit testing NHibernate interactions with SQLite)


問題描述

使用 MbUnit3 的 [Rollback] 對 NHibernate 與 SQLite 的交互進行單元測試 (Using MbUnit3's [Rollback] for unit testing NHibernate interactions with SQLite)

Background:

My team is dedicated to ensuring that straight from checkout, our code compiles and unit tests run successfully.  To facilitate this and test some of our NHibernate mappings, we've added a SQLite DB to our repository which is a mirror of our production SQL Server 2005 database.  We're using the latest versions of: MbUnit3 (part of Gallio), System.Data.SQLite and NHibernate.

Problem:

I've discovered that the following unit test does not work with SQLite, despite executing without trouble against SQL Server 2005.

    [Test]
    [Rollback]
    public void CompleteCanPersistNode()
    {
        // returns a Configuration for either SQLite or SQL Server 2005 depending on how the project is configured.
        Configuration config = GetDbConfig(); 

        ISessionFactory sessionFactory = config.BuildSessionFactory();
        ISession session = sessionFactory.OpenSession();

        Node node = new Node();
        node.Name = "Test Node";
        node.PhysicalNodeType = session.Get<NodeType>(1);

        // SQLite fails with the exception below after the next line called.
        node.NodeLocation = session.Get<NodeLocation>(2);

        session.Save(node);
        session.Flush();

        Assert.AreNotEqual(‑1, node.NodeID);
        Assert.IsNotNull(session.Get<Node>(node.NodeID));
    }

The exception I'm getting (ONLY when working with SQLite) follows:

NHibernate.ADOException: cannot open connection ‑‑‑>
System.Data.SQLite.SQLiteException:
  The database file is locked database is locked
    at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt)
    at System.Data.SQLite.SQLiteDataReader.NextResult()
    at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
    at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
    at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery()
    at System.Data.SQLite.SQLiteTransaction..ctor(SQLiteConnection connection, Boolean deferredLock)
    at System.Data.SQLite.SQLiteConnection.BeginDbTransaction(IsolationLevel isolationLevel)
    at System.Data.SQLite.SQLiteConnection.BeginTransaction()
    at System.Data.SQLite.SQLiteEnlistment..ctor(SQLiteConnection cnn, Transaction scope)
    at System.Data.SQLite.SQLiteConnection.EnlistTransaction(Transaction transaction)
    at System.Data.SQLite.SQLiteConnection.Open()
    at NHibernate.Connection.DriverConnectionProvider.GetConnection()
    at NHibernate.Impl.SessionFactoryImpl.OpenConnection()
    ‑‑‑ End of inner exception stack trace ‑‑‑
    at NHibernate.Impl.SessionFactoryImpl.OpenConnection()
    at NHibernate.AdoNet.ConnectionManager.GetConnection()
    at NHibernate.AdoNet.AbstractBatcher.Prepare(IDbCommand cmd)
    at NHibernate.AdoNet.AbstractBatcher.ExecuteReader(IDbCommand cmd)
    at NHibernate.Loader.Loader.GetResultSet(IDbCommand st, Boolean autoDiscoverTypes, Boolean callable, RowSelection selection, ISessionImplementor session)
    at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
    at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
    at NHibernate.Loader.Loader.LoadEntity(ISessionImplementor session, Object id, IType identifierType, Object optionalObject, String optionalEntityName, Object optionalIdentifier, IEntityPersister persister)
    at NHibernate.Loader.Entity.AbstractEntityLoader.Load(ISessionImplementor session, Object id, Object optionalObject, Object optionalId)
    at NHibernate.Loader.Entity.AbstractEntityLoader.Load(Object id, Object optionalObject, ISessionImplementor session)
    at NHibernate.Persister.Entity.AbstractEntityPersister.Load(Object id, Object optionalObject, LockMode lockMode, ISessionImplementor session)
    at NHibernate.Event.Default.DefaultLoadEventListener.LoadFromDatasource(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options)
    at NHibernate.Event.Default.DefaultLoadEventListener.DoLoad(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options)
    at NHibernate.Event.Default.DefaultLoadEventListener.Load(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options)
    at NHibernate.Event.Default.DefaultLoadEventListener.ProxyOrLoad(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options)
    at NHibernate.Event.Default.DefaultLoadEventListener.OnLoad(LoadEvent event, LoadType loadType)
    at NHibernate.Impl.SessionImpl.FireLoad(LoadEvent event, LoadType loadType)
    at NHibernate.Impl.SessionImpl.Get(String entityName, Object id)
    at NHibernate.Impl.SessionImpl.Get(Type entityClass, Object id)
    at NHibernate.Impl.SessionImpl.Get[T](Object id)
D:\dev\598\Code\test\unit\DataAccess.Test\NHibernatePersistenceTests.cs

When SQLite is used and the [Rollback] attribute is NOT specified, the test also completes successfully.

Question:

Is this an issue with System.Data.SQLite's implementation of TransactionScope which MbUnit3 uses for [Rollback] or a limitation of the SQLite engine?  

Is there some way to write this unit test, working against SQLite, that will rollback so as to avoid affecting the database each time the test is run?

‑‑‑‑‑

參考解法

方法 1:

This is not a real answer to you question, but probably a solution to solve the problem.

I use an in‑memory implementation of sql lite for my integration tests. I build up the schema and fill the database before each test. The schema creation and initial data filling happens really fast (less then 0.01 seconds per test) because it's an in‑memory database.

Why do you use a physical database?

Edit: response to answer about question above:

1.) Because I migrated my schema and data directly from SQL Server 2005 and I want it to persist in source control.

  • I recommend to store a file with the database schema in and a file or script that creates the sample data in source control. You can generate the file using sql server studion management express, you can generate it from your NHibernate mappings or you can use a tool like sql compare and you can probably find other solutions for this when you need it. Plain text files are stored easier in version control systems then complete binary database files. 

2.) Does something about the in‑memory SQLite engine differ such that it would resolve this difficulty?

  • It might solve your problems because you can recreate your database before each test. Your database under test will be in a the state you expect it to be before each test is executed. A benefit of that is there is no need to roll back your transactions, but I have run similar test with in memory sqllite and it worked as aspected.

方法 2:

Check if you're not missing connection.release_mode=on_close in your SQLite NHibernate configuration. (reference docs)

BTW: always dispose your ISession and ISessionFactory.

方法 3:

Ditch [Rollback] and use NDbUnit. I use this myself for this exact scenario and it has been working great.

(by antikPacoMauricio Schefferremotefacade)

參考文件

  1. Using MbUnit3's [Rollback] for unit testing NHibernate interactions with SQLite (CC BY‑SA 3.0/4.0)

#mbunit #gallio #sqlite #unit-testing #nhibernate






相關問題

我可以圍繞 NUnit、MbUnit、xUnit 或其他測試框架創建一個包裝器嗎? (Can I create a wrapper around NUnit, MbUnit, xUnit or other testing framework?)

MbUnit / Gallio 上的 FixtureSetup 未運行 (FixtureSetup on MbUnit / Gallio doesn't run)

我可以使用我的 WatiN 測試來進行壓力測試嗎? (Can i use my WatiN tests to stresstest?)

Debug Unit Test bằng cách sử dụng Resharper 7 cho MBUnit ném ra một ngoại lệ (Debug Unit Tests using Resharper 7 for MBUnit throws an exception)

TestDriven.NET沒有運行我的MbUnit的SetUp方法 (TestDriven.NET is not running my SetUp methods for MbUnit)

Gallio用戶,您使用此工具有哪些優缺點? (Users of Gallio, what Advantages and Disadvantages have you experienced using this Tool?)

你知道任何關於 MBUnit 的教程嗎? (do you know any tutorial for MBUnit?)

MbUnit:比較雙打最優雅的方式? (MbUnit: The most elegant way to compare doubles?)

如何將 MBUnit 測試添加到 CruiseControl.Net 配置文件 (how to add MBUnit test to CruiseControl.Net config file)

MbUnit 回滾 (MbUnit Rollback)

使用 MbUnit3 的 [Rollback] 對 NHibernate 與 SQLite 的交互進行單元測試 (Using MbUnit3's [Rollback] for unit testing NHibernate interactions with SQLite)

MbUnit.framework.dll 可並行化屬性 (MbUnit.framework.dll Parallelizable attribute)







留言討論