針對資料存取碼的單元測試處在一個尷尬的位置上,如果操作不是針對真實的資料庫執行的,就無法捕獲資料庫特定的錯誤,比如 sql 語句文法是否正確,操作是否違反了資料庫約束,事務是否正確提交。並且,測試之間應該是隔離的,一個測試不能影響另一個測試的資料,就是說,每個測試回合之前都要重建表結構,重新安裝測試資料。在一個真實的資料庫上執行這些操作會讓測試成為老牛破車。
所幸的是Sqlite提供了記憶體資料庫,避免磁碟IO可以帶來效能提升。記憶體資料庫有一個非常重要的特點:即資料庫僅在串連開啟的時候存在,一旦串連關閉,資料庫隨即消失。這正是我們想要的,運行測試的步驟如下:
1,在 [TestInitialize] 方法中開啟 Session,建立表結構,安裝測試資料。預設情況下,Sqlite的Unicode字串比較是區分大小寫,所以,建立表結構的時候要為 TEXT 列指定 COLLATE NOCASE。
2,運行測試
3,在 [TestCleanup] 方法中關閉 Session,這將導致底層的串連關閉,記憶體資料庫消失。
代碼如下:
[TestClass] public class MyTest { [AssemblyInitialize] public static void InitAssembly(TestContext testContext) { NHibernateHelper.Init(); } [AssemblyCleanup] public static void CleanupAssembly() { NHibernateHelper.CloseSessionFactory(); } protected ISession Session { get; private set; } [TestInitialize()] public void MyTestInitialize() { try { // 1 開啟 Session Session = NHibernateHelper.OpenSession(); // 2 建立表 NHibernate.Tool.hbm2ddl.SchemaExport export = new NHibernate.Tool.hbm2ddl.SchemaExport(NHibernateHelper.Configuration); export.SetDelimiter(";"); StringWriter sw = new StringWriter(); export.Execute(false, false, false, Session.Connection, sw); using (IDbCommand cmd = Session.Connection.CreateCommand()) { // 替換欄位定義語句 cmd.CommandText = Regex.Replace(sw.ToString(), @"\s+TEXT\s+", " TEXT COLLATE NOCASE ", RegexOptions.IgnoreCase | RegexOptions.Compiled); cmd.ExecuteNonQuery(); } // 3 建立測試資料 using (ITransaction tx = Session.BeginTransaction()) { Role role = new Role(); role.Name = "admins"; Session.Save(role); tx.Commit(); } // 4 清除 Session 緩衝 Session.Clear(); } catch (Exception ex) { // 如果發生異常,則 TestCleanup 不會執行,因此在這裡回收資源 if (Session != null) { Session.Close(); } throw; } } [TestCleanup()] public void MyTestCleanup() { Session.Close(); Session = null; } [TestMethod] public void MyTestMethod() { using (ITransaction tx = Session.BeginTransaction()) { Role role = Session.Query<Role>().FirstOrDefault(x => x.Name == "admins"); Assert.IsNotNull(role); tx.Commit(); } } }
NHibernate 配置,注意 connection.release_mode 屬性一定要設為 on_close
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <bytecode-provider type="null"/> <reflection-optimizer use="false"/> <session-factory> <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property> <property name="connection.driver_class">NHibernate.Driver.SQLite20Driver</property> <property name="connection.connection_string"> data source=:memory: </property> <property name="dialect">NHibernate.Dialect.SQLiteDialect</property> <property name="connection.release_mode">on_close</property> <property name="hbm2ddl.keywords">none</property> <property name="current_session_context_class">managed_web</property> <property name="show_sql">false</property> <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property> <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property> <mapping assembly="WuTong"/> </session-factory></hibernate-configuration>
參考連結:
http://ayende.com/blog/3983/nhibernate-unit-testing
http://www.sqlite.org/faq.html#q18