使用MbUnit3的[Rollback]进行单元测试NHibernate与SQLite的交互

时间:2009-03-27 19:21:03

标签: unit-testing nhibernate sqlite mbunit gallio

背景

我的团队致力于确保直接结账,我们的代码编译和单元测试成功运行。为了实现这一点并测试我们的一些NHibernate映射,我们在我们的存储库中添加了一个SQLite DB,它是我们的生产SQL Server 2005数据库的镜像。我们使用的是最新版本:MbUnit3(Gallio的一部分),System.Data.SQLite和NHibernate。

问题:

我发现以下单元测试不能与SQLite一起使用,尽管对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));
    }

我得到的异常(仅在使用SQLite时)如下:

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

当使用SQLite并且未指定[Rollback]属性时,测试也会成功完成。

问题:

这是一个问题,System.Data.SQLite的TransactionScope实现是MbUnit3用于[Rollback]还是限制了SQLite引擎?

有没有办法编写这个单元测试,反对SQLite,它会回滚,以避免每次运行测试时都影响数据库?

3 个答案:

答案 0 :(得分:2)

这不是你问题的真正答案,但可能是解决问题的解决方案。

我使用sql lite的内存实现来进行集成测试。我在每次测试之前构建模式并填充数据库。模式创建和初始数据填充发生得非常快(每次测试少于0.01秒)因为它是内存数据库。

为什么使用物理数据库?

编辑:对上述问题的回答回复:

1。)因为我直接从SQL Server 2005迁移了我的架构和数据,我希望它在源代码控制中持久存在。

  • 我建议存储包含数据库架构的文件以及在源代码管理中创建示例数据的文件或脚本。你可以使用sql server studion management express生成文件,你可以从你的NHibernate映射生成它,或者你可以使用像sql compare这样的工具,你可以在需要时找到其他解决方案。纯文本文件在版本控制系统中更容易存储,然后完成二进制数据库文件。

2。)内存中的SQLite引擎有什么不同之处,以至于它可以解决这个难题吗?

  • 它可以解决您的问题,因为您可以在每次测试之前重新创建数据库。在执行每个测试之前,您测试的数据库将处于您期望的状态。这样做的一个好处就是不需要回滚你的事务,但是我已经在内存sqllite中运行了类似的测试,并且它可以正常工作。

答案 1 :(得分:1)

检查您的SQLite NHibernate配置中是否遗漏了connection.release_mode=on_close。 (reference docs

BTW:始终处置您的ISessionISessionFactory

答案 2 :(得分:0)

Ditch [Rollback]并使用NDbUnit。我自己就是用这个确切的场景而且它一直很好用。