概要
xUnitでユニットテストをする際、テスト実行のためにデータベースが必要となるテストケースをどのように書いたら良いかまとめました。
ここではNHibernate + SQLiteの組み合わせの場合のコードを例示していますが、Entity Framework + SQL Databaseなどの場合も同じような考え方で実装できます。
実装方針
テストケースを記述したクラスでIClassFixture<TFixture>
を継承しておくと、xUnitによってテストが実行される前にTFixture
クラスがインスタンス化され、そのインスタンスがテストケースのクラスのコンストラクタに渡されます。
従って、今回のようにデータベースを必要とするテストでは、次のような方針でテストコードを実装します。
-
TFixture
クラスにDB周りの初期化コードを書く - テストケースのクラスでは、
IClassFixture<TFixture>
を継承して、コンストラクタでTFixtureインスタンスを受け取る - テストコード内でTFixtureインスタンスからDBのセッションを取り出してDB操作する
実際のコード例
SQLiteのセッションを生成するためのFixtureクラス例
Fixtureクラス側では、インメモリなSQLiteに接続するようなコードを、NHibernateとFluentNHibernateを使って実装しました。
なお、IDisposableを実装しておくと、xUnitがテスト終了時にDispose()を呼び出してくれるため、DBの接続を閉じるコードなどを書いておくことができます。
またSchemaExport
を利用し、O/Rマッパーで定義されているマッピング情報を元に、SQLite上に必要なテーブルを自動的に作った上でDBセッションをテストケースに渡す、ということも実装しています。
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.PlatformAbstractions;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using System;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Abstractions;
public class SQLiteInMemoryDatabaseFixture: IDisposable
{
private static ISessionFactory sessionFactory;
private static Configuration configuration;
public static ISessionFactory SessionFactory {
get
{
if (sessionFactory == null)
{
sessionFactory = Fluently.Configure()
.Database(
SQLiteConfiguration.Standard
.InMemory()
.ShowSql()
)
.Mappings(m =>
{
m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
})
.ExposeConfiguration(cfg => {
configuration = cfg;
})
.BuildSessionFactory();
}
return sessionFactory;
}
}
public ISession GetSession()
{
ISession session = SessionFactory.OpenSession();
var export = new SchemaExport(configuration);
export.Execute(true, true, false, session.Connection, null);
return session;
}
#region IDisposable Support
private bool disposedValue = false; // 重複するDispose()呼び出しを検出する
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
if (sessionFactory != null)
sessionFactory.Dispose();
}
sessionFactory = null;
configuration = null;
disposedValue = true;
}
}
//IDisposableを実装しておくと、xUnitがテスト終了時に呼び出してくれる
// https://xunit.github.io/docs/shared-context.html#class-fixture
public void Dispose()
{
Dispose(true);
}
#endregion
}
テストケースでFixtureクラスを利用する
テストケースがIClassFixture<TFixture>
を継承していると、テストケースのコンストラクタにTFixture
のインスタンスが渡されます。
渡されたfixtureをフィールドに格納しておき、テストケース内から適宜利用する形になります。
public class MyServiceTest: IClassFixture<SQLiteInMemoryDatabaseFixture>
{
private readonly SQLiteInMemoryDatabaseFixture _fixture;
public MyServiceTest(SQLiteInMemoryDatabaseFixture fixture)
{
_fixture = fixture;
}
[Fact]
public void InterfaceImplimentationTest()
{
var session = _fixture.GetSession();
var uowFactory = new NHibernateUnitOfWorkFactory(session: session);
// テストケースが続く.......
}
}
利用したNuGetライブラリ
project.jsonで読み込んでいるライブラリはこんな感じです。
"dependencies": {
"NHibernate": "4.0.4.4000",
"xunit": "2.1.0",
"dotnet-test-xunit": "1.0.0-rc2-build10025",
"xunit.runner.visualstudio": "2.1.0",
"FluentNHibernate": "2.0.3",
"FluentMigrator": "1.6.2",
"FluentMigrator.Tools": "1.6.2",
"System.Data.SQLite": "1.0.101",
"System.Data.SQLite.Core": "1.0.101",
"System.Data.SQLite.Linq": "1.0.101",
"System.Data.SQLite.x64": "1.0.101"
}