Copy in-memory SQLite Database to make unit tests faster

坚强是说给别人听的谎言 提交于 2020-08-02 02:52:04

问题


In C#/nHibernate-projects I am using SQLite to unit test my code, aproximately using the method described here: http://ayende.com/blog/3983/nhibernate-unit-testing.

However, I find that building and configuring the in-memory database typically takes about 150ms. I have lots of unit test so this rapidly adds up.

I want to build and configure the database once, store it in a static variable, and copy it every time a unit test needs a database.

How do I back-up an in-memory database?

I first tried to create a named in-memory database. According to https://www.sqlite.org/inmemorydb.html this is possible. I used to have:

    private const string ConnectionString = "Data Source=:memory:;Version=3;";

Connection strings I tried are:

    private const string ConnectionString = "FullUri=file:memorydb.db?mode=memory&cache=shared";
    private const string ConnectionString2 = "FullUri=file:memorydb2.db?mode=memory&cache=shared";

So now I just have to find out how to quickly copy content from one to another? I'm almost there: I can create two in-memory databases, and call "BackupDatabase" to copy the database.

The unit test however, behaves like the "instance" database has no tables, even the "prototype" database does.

        private static ISessionFactory _prototypeSessionFactory;
        private const string InstanceConnectionString = "FullUri=file:memorydb.db?mode=memory&cache=shared";

        private const string PrototypeConnectionString = "FullUri=file:memorydb2.db?mode=memory&cache=shared";
        private SQLiteConnection _instanceConnection;
        private ISessionFactory _instanceSessionFactory;

        public DatabaseScope(Assembly assembly)
        {
            var prototyeConfiguration = SQLiteConfiguration.Standard.ConnectionString(PrototypeConnectionString);
            var cfg = Fluently
                .Configure()
                .Database(prototyeConfiguration)
                .Mappings(m => m.HbmMappings.AddFromAssembly(assembly));
            cfg.ExposeConfiguration(BuildSchema);
            _prototypeSessionFactory = cfg.BuildSessionFactory();

            var instanceConfiguration = SQLiteConfiguration.Standard.ConnectionString(InstanceConnectionString);
            _instanceSessionFactory = Fluently
                .Configure()
                .Database(instanceConfiguration)
                .BuildSessionFactory();

            CopyDatabase();
        }

        private void CopyDatabase()
        {
            var cnnIn = new SQLiteConnection(PrototypeConnectionString);
            var cnnOut = new SQLiteConnection(InstanceConnectionString);
            cnnIn.Open();
            cnnOut.Open();
            cnnIn.BackupDatabase(cnnOut, "main", "main", -1, null, -1);
            cnnIn.Close();
            cnnOut.Close();
        }

回答1:


I ended up with this working code. My unit test duration went from over ten minutes to under two minutes. (Code slightly simplified for readability)

using System;
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Reflection;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Mapping;
using NHibernate.Tool.hbm2ddl;

namespace TestHelper.DbHelper.SqLite
{
    public class DatabaseScope : IDisposable
    {
        private static Assembly _prototypeAssembly;
        private const string PrototypeConnectionString = "FullUri=file:prototype.db?mode=memory&cache=shared";
        private static ISessionFactory _prototypeSessionFactory;
        private static SQLiteConnection _prototypeConnection;

        private const string InstanceConnectionString = "FullUri=file:instance.db?mode=memory&cache=shared";
        private ISessionFactory _instanceSessionFactory;
        private SQLiteConnection _instanceConnection;

        public DatabaseScope(Assembly assembly)
        {
            InitDatabasePrototype(assembly);
            InitDatabaseInstance();
        }

        private void InitDatabasePrototype(Assembly assembly)
        {
            if (_prototypeAssembly == assembly) return;

            if (_prototypeConnection != null)
            {
                _prototypeConnection.Close();
                _prototypeConnection.Dispose();
                _prototypeSessionFactory.Dispose();
            }

            _prototypeAssembly = assembly;

            _prototypeConnection = new SQLiteConnection(PrototypeConnectionString);
            _prototypeConnection.Open();

            _prototypeSessionFactory = Fluently
                .Configure()
                .Database(SQLiteConfiguration.Standard.ConnectionString(PrototypeConnectionString))
                .Mappings(m => m.HbmMappings.AddFromAssembly(assembly))
                .ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(false, true, false, _prototypeConnection, null))
                .BuildSessionFactory();
        }

        private void InitDatabaseInstance()
        {
            _instanceSessionFactory = Fluently
                .Configure()
                .Database(SQLiteConfiguration.Standard.ConnectionString(InstanceConnectionString))
                .Mappings(m => m.HbmMappings.AddFromAssembly(_prototypeAssembly))
                .BuildSessionFactory();

            _instanceConnection = new SQLiteConnection(InstanceConnectionString);
            _instanceConnection.Open();

            _prototypeConnection.BackupDatabase(_instanceConnection, "main", "main", -1, null, -1);
        }

        public ISession OpenSession()
        {
            return _instanceSessionFactory.OpenSession(_instanceConnection);
        }

        public void Dispose()
        {
            _instanceConnection.Close();
            _instanceConnection.Dispose();
            _instanceSessionFactory.Dispose();
        }
    }
}



回答2:


What I have observed with SQLite in memory databases is that as soon as you close the connection, everything in the db is gone. So to do what you want,

  1. Create session factory for the backup database, open session and build schema don't close this session until you finish your entire test suite

  2. Create session factory for your target database, open session and use the connection from this session object and the connection from session created from step 1 to copy data

  3. Use the session created on step 2 for test and close it once test is finished

Another solution is to use the single session to perform multiple tests (all the tests in single test fixture) then you do not need to create new session factory per test, but per testfixture



来源:https://stackoverflow.com/questions/31073478/copy-in-memory-sqlite-database-to-make-unit-tests-faster

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!