找回密码
 立即注册
首页 业界区 业界 【翻译】How-To: Using the N* Stack, part 4

【翻译】How-To: Using the N* Stack, part 4

甄婉丽 2025-5-29 20:01:58
原文地址:Part 4: Persistence tests
此篇教程中,我们将测试之前建立的映射并且进一步的了解 NHibernate 。
我们使用的是 NUnit 2.5.2 ,不过新版本的也应该会兼容的。
声明: 此部分内容我自己也是在学习中,所以你可以继续学习,不过风险自负,这可能不是最佳做法。此外,还有很多流行的测试框架可以测试我们的程序,但我们这里使用 NUnit 。
我们新建一个类库项目,用于我们测试,起名叫 NStackExample.Data.Tests 。为这个项目添加引用,将 core 工程、 data 工程、 NHibernate.dll 、 FluentNHibernate.dll 和 NUnit.Framework.dll 添加进来。如果你安装了 NUnit , NUnit.Framework.dll 可以在 .NET 选项卡里看到。如果你安装了多个版本的 NUnit 的话,一定要注意选择的版本。
SQL: 在一个方便的旅行装

如果你之前没有听说过 SQLite ,你会喜欢上它的。这是一个很小的、独立的、并且开源的SQL数据库引擎。更好的是,它可以完全的运行在内存当中并且速度非常快。下面是如何设置并且使用它:

  • 下载 SQLite 提供的 ADO.NET Provider 。下载完整版,文件名类似 SQLLit-1.0.65.0-setup.exe。安装完成之后,将 System.Data.Sqlite.dll 拷贝到Solution Items 文件夹,并且添加到你所有工程的引用当中。
  • 下载 SQLite 类库。文件名类似 sqlitedll-3_6_17.zip。提取 SQLite3.dll 到 Solution Items 文件夹。
  • 将 System.Data.SQLite.dll 添加到测试工程的引用中。
  • 由于 SQLite3.dll 是用 C 编写的,是非托管的,我们不能直接引用它。为了正确的引用它,我们需要其设置成为内容文件。在测试项目上点击右键,选择添加现有项,添加 SQLite3.dll 。在解决方案资源管理器中找到这个文件,点击右键选择属性,将它设置为始终复制。这样就会在每次生成项目的时候会自动的将这个文件复制到 bin\Debug 或 bin\Release 目录里。
  • 在我以前的随笔里,有一个 SQLiteDatabaseScope 类的代码,你可以把它添加到你的测试工程里。
一个简单的映射测试

 
  1. using System;
  2. using System.Linq;
  3. using NUnit.Framework;
  4. using NHibernate;
  5. namespace NStackExample.Data.Tests
  6. {
  7.     [TestFixture]
  8.     public class CourseMappingTests
  9.     {
  10.         [Test]
  11.         public void CanSaveAndLoadCourse()
  12.         {
  13.             using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
  14.             {
  15.                 using (ISession Session = Scope.OpenSession())
  16.                 {
  17.                     Guid ID;
  18.                     Course Course;
  19.                     using (ITransaction Tran = Session.BeginTransaction())
  20.                     {
  21.                         ID = (Guid)Session.Save(new Course
  22.                         {
  23.                             Subject = "SUBJ",
  24.                             CourseNumber = "1234",
  25.                             Title = "Title",
  26.                             Description = "Description",
  27.                             Hours = 3
  28.                         });
  29.                         Tran.Commit();
  30.                     }
  31.                     Session.Clear();
  32.                     using (ITransaction Tran = Session.BeginTransaction())
  33.                     {
  34.                         Course = Session.Get<Course>(ID);
  35.                         Assert.AreEqual("SUBJ", Course.Subject);
  36.                         Assert.AreEqual("1234", Course.CourseNumber);
  37.                         Assert.AreEqual("Title", Course.Title);
  38.                         Assert.AreEqual("Description", Course.Description);
  39.                         Assert.AreEqual(3, Course.Hours);
  40.                         Tran.Commit();
  41.                     }
  42.                 }
  43.             }
  44.         }
  45.     }
  46. }
复制代码
 
 
下面介绍下它如何工作:

  • 首先,在建立架构的时候获取到一个新的内存中的 SQLite 数据库
  • 将 course 保存到数据库中
  • 清空 session
  • 从数据库中获取到 course
  • 测试所有属性,以确保它们都是正确的
这里你需要知道几件事

  • TestFixture 属性。这是告诉 NUnit 在这个类当中包含测试。
  • Test 属性。这是告诉 NUnit 这个方法是一个测试方法。
建议不要使用隐式事务

你可能在疑惑为什么我将这些简单的数据库逻辑封装在一个事务中,特别是 Session.Get ,这只是一个单一的选择语句。在写本系列教程之前,我也不会这样做的,这是一个新手的错误。
在做这项研究的时候,我看到了 Ayende 写的  一个测试示例 。他对所有的操作都使用事务,甚至是 Session.Get 。我问他为什么这样做的时候,他给我发了一个链接,NHProfiler Alert 。这一点是非常重要的,而且不是显而易见的,至少对我来说是这样的。具体的内容大家可以去之前的链接看一下。
一个稍微复杂的映射测试

在某些时候我们的实体需要有一个父亲,例如 section ,所以必须在测试孩子之前需要先创建并插入父亲。我们不在此进行级联测试,这是一个单独的测试。在这里, section 必须要有两个父亲:course 和 term。
  1.         [Test]
  2.         public void CanSaveAndLoadSection()
  3.         {
  4.             using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>()) {
  5.                 using (ISession Session = Scope.OpenSession()) {
  6.                     Guid ID;
  7.                     Section Section;
  8.                     Course Course = new Course {
  9.                         Subject = "SUBJ",
  10.                         CourseNumber = "1234",
  11.                         Title = "Title",
  12.                         Description = "Description",
  13.                         Hours = 3};
  14.                     Term Term = new Term {
  15.                         Name = "Fall 2009",
  16.                         StartDate = new DateTime(2009,8,1),
  17.                         EndDate = new DateTime(2009,12,1)};
  18.                     
  19.                     // 我们不在这里进行级联测试,所以显式的保存父对象。
  20.                     using (ITransaction Tran = Session.BeginTransaction()) {
  21.                         Session.Save(Course);
  22.                         Session.Save(Term);
  23.                         Tran.Commit();
  24.                     }
  25.                     Session.Clear();
  26.                     using (ITransaction Tran = Session.BeginTransaction()) {
  27.                         ID = (Guid) Session.Save(new Section {
  28.                                  Course = Course,
  29.                                  FacultyName = "FacultyName",
  30.                                  RoomNumber = "R1",
  31.                                  SectionNumber = "W1",
  32.                                  Term = Term});
  33.                         Tran.Commit();
  34.                     }
  35.                     Session.Clear();
  36.                     using (ITransaction Tran = Session.BeginTransaction()) {
  37.                         Section = Session.Get<Section>(ID);
  38.                         Assert.AreEqual(Course, Section.Course);
  39.                         Assert.AreEqual("FacultyName", Section.FacultyName);
  40.                         Assert.AreEqual("R1",Section.RoomNumber);
  41.                         Assert.AreEqual("W1", Section.SectionNumber);
  42.                         Assert.AreEqual(Term, Section.Term);
  43.                         Tran.Commit();
  44.                     }
  45.                 }
  46.             }
  47.         }
复制代码
级联测试

“级联什么? “
在应用程序中, 当你对子对象进行了一些改变,你肯定不希望需要自己去想着还需要对父对象进行哪些操作,这样是非常繁琐的。好在 NHibernate 提供了级联的功能,你不需要做这些事了。如果你的映射是正确的,你就只需要保存子对象,其他的工作你都不需要理会。
对于一些人,尤其像我这样的,这是一个很大的工作量,这就是为什么我们要测试映射。
  1. [Test()]
  2. public void CanCascadeSaveFromCourseToSections()
  3. {
  4.     using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
  5.     {
  6.         using (ISession Session = Scope.OpenSession())
  7.         {
  8.             Guid ID;
  9.             Term Term = new Term {
  10.                         Name = "Fall 2009",
  11.                         StartDate = new System.DateTime(2009, 9, 1),
  12.                         EndDate = new System.DateTime(2009, 12, 1) };
  13.             //We're not testing the cascade of section -> term here
  14.             using (ITransaction Tran = Session.BeginTransaction())
  15.             {
  16.                 Session.Save(Term);
  17.                 Tran.Commit();
  18.             }
  19.             Session.Clear();
  20.             Course Course = new Course {
  21.                 Subject = "SUBJ",
  22.                 CourseNumber = "1234",
  23.                 Title = "Title",
  24.                 Description = "Description",
  25.                 Hours = 3 };
  26.             Section Section1 = new Section {
  27.                 FacultyName = "FacultyName",
  28.                 RoomNumber = "R1",
  29.                 SectionNumber = "1",
  30.                 Term = Term };
  31.             Section Section2 = new Section {
  32.                 FacultyName = "FacultyName",
  33.                 RoomNumber = "R1",
  34.                 SectionNumber = "2",
  35.                 Term = Term };
  36.             Course.AddSection(Section1);
  37.             Course.AddSection(Section2);
  38.             //Test saving
  39.             using (ITransaction Tran = Session.BeginTransaction())
  40.             {
  41.                 ID = (Guid) Session.Save(Course);
  42.                 Tran.Commit();
  43.             }
  44.             Session.Clear();
  45.             //Check the results
  46.             using (ITransaction Tran = Session.BeginTransaction())
  47.             {
  48.                 Course = Session.Get<Course>(ID);
  49.                 Assert.AreEqual(2, Course.Sections.Count);
  50.                 Assert.AreEqual(1, Course.Sections
  51.                         .Where(S => S.Equals(Section1)).Count(),
  52.                         "Course.Sections does not contain section 1.");
  53.                 Assert.AreEqual(1, Course.Sections
  54.                         .Where(S => S.Equals(Section2)).Count(),
  55.                         "Course.Sections does not contain section 2.");
  56.                 Tran.Commit();
  57.             }
  58.         }
  59.     }
  60. }
复制代码
上面的测试将确保档你保存 course 的时候,也同样会保存对 section 的操作。下面看看它是如何工作的:

  • 获取一个新的 SQLite 数据库
  • 虽然我们不会测试 term ,但是因为 section 的需要,所以我们也需要在数据库中建立
  • 创建一个 course 和两个 section
  • 保存 course
  • 清空 session
  • 获取到 course
  • 确保它有两个 section
当你从 course 中移除一个 section 将会发生什么呢?当然,任何一个 section 都需要有一个父对象,也就是 course 。请记住,我们在映射里指定的是非空的。更重要的是,现实世界中不会有独立的  section 。所以,当 section 成为孤儿的时候,它将被删除掉,下面我们来进行这样的一个测试:
  1. [Test()]
  2. public void CanCascadeOrphanDeleteFromCourseToSections()
  3. {
  4.     using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
  5.     {
  6.         using (ISession Session = Scope.OpenSession())
  7.         {
  8.             Guid ID;
  9.             Term Term = new Term {
  10.                 Name = "Fall 2009",
  11.                 StartDate = new System.DateTime(2009, 9, 1),
  12.                 EndDate = new System.DateTime(2009, 12, 1) };
  13.             using (ITransaction Tran = Session.BeginTransaction())
  14.             {
  15.                 //We're not testing the cascade of section -> term here
  16.                 Session.Save(Term);
  17.                 Tran.Commit();
  18.             }
  19.             Session.Clear();
  20.             Course Course = new Course {
  21.                 Subject = "SUBJ",
  22.                 CourseNumber = "1234",
  23.                 Title = "Title",
  24.                 Description = "Description",
  25.                 Hours = 3 };
  26.             Section Section1 = new Section {
  27.                 FacultyName = "FacultyName",
  28.                 RoomNumber = "R1",
  29.                 SectionNumber = "1",
  30.                 Term = Term };
  31.             Section Section2 = new Section {
  32.                 FacultyName = "FacultyName",
  33.                 RoomNumber = "R1",
  34.                 SectionNumber = "2",
  35.                 Term = Term };
  36.             Course.AddSection(Section1);
  37.             Course.AddSection(Section2);
  38.             using (ITransaction Tran = Session.BeginTransaction())
  39.             {
  40.                 Session.Save(Course);
  41.                 Tran.Commit();
  42.             }
  43.             Session.Clear();
  44.             //Test removing
  45.             Course.RemoveSection(Section1);
  46.             using (ITransaction Tran = Session.BeginTransaction())
  47.             {
  48.                 ID = (Guid) Session.Save(Course);
  49.                 Tran.Commit();
  50.             }
  51.             Session.Clear();
  52.             //Check the results
  53.             using (ITransaction Tran = Session.BeginTransaction())
  54.             {
  55.                 Course = Session.Get<Course>(ID);
  56.                 Assert.AreEqual(1, Course.Sections.Count());
  57.                 Assert.AreEqual(0, Course.Sections
  58.                     .Where(S => S.Equals(Section1)).Count(),
  59.                     "Course.Sections still contains section 1");
  60.                 Tran.Commit();
  61.             }
  62.         }
  63.     }
  64. }
复制代码
我希望大家能和我是一样的心态,除了查询测试,当我们写 DAO 层的时候,就要对 NHibernate 进行测试。我们对其他的实体类也都要进行相同类型的测试。
但是…

那么,你肯定在想“这么做太混乱了,肯定无法编译,就算可以,差不多所有的测试都会失败!”是的,如果测试永远都是通过的,我们还做这些干什么呢?
通常情况下,我至少会声明出这些缺少的函数,让解决方案编译通过。在接下来的话题中我们就会讨论这个问题:如何修复错误的资料?
示例代码下载:/Files/LeoXing/NStack/NStackExample.Part4.CSharp.zip

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册