如何对实体框架代码优先映射进行单元测试

本文关键字:映射 单元测试 代码 实体 框架 | 更新日期: 2023-09-27 18:32:26

我正在使用Code First将类映射到现有数据库。 我需要一种方法来对这些映射进行单元测试,这些映射是基于约定、基于属性和流畅 API 的混合体。

为了进行单元测试,我需要确认类的属性映射到数据库中正确的表名和列名。 此测试需要针对上下文执行,并且应首先涵盖代码的所有配置选项。

在非常高的层次上,我希望断言类似的东西(伪代码):

Assert.IsTrue(context.TableFor<Widget>().IsNamed("tbl_Widget"));
Assert.IsTrue(context.ColumnFor<Widget>(w => w.Property).IsNamed("WidgetProperty"));

如何对实体框架代码优先映射进行单元测试

另一个需要考虑的想法是使用 Linq 和 ToString()。

对于这个:

context.Widget.Select(c => c.Property).ToString()

将导致 SQL 服务器提供程序出现此问题:

"SELECT [Var_3].[WidgetProperty] AS [WidgetProperty] FROM [dbo].[Widget]..."

现在我们可以将其全部隐藏在某个扩展方法中,并解析生成的 SQL,它看起来几乎像您的伪代码:

Assert.IsTrue(context.Widgets.GetSqlColumnNameFor(w => w.Property).IsNamed("WidgetProperty"));

延期草案 :

public string GetSqlColumnNameFor<TSource>(this DbSet<T> source, Expression<Func<TSource, TResult>> selector)
{
    var sql = source.Select(selector).ToString();
    var columnName = sql... // TODO : Some regex parsing
    return 
       columnName;
}

类似地,我们可以创建 GetSqlTableNameFor()。

更新:我决定寻找一些专用的SQL解析器,所以这个解决方案更通用,显然.NET有这样的事情:

http://www.dpriver.com/blog/list-of-demos-illustrate-how-to-use-general-sql-parser/generate-internal-query-parse-tree-in-xml-for-further-processing/

我能想到的涵盖所有可能选项的唯一方法是使用实体框架高级工具来预编译 DbContext 的视图,并可能结合使用对该生成类型的反射和对生成的代码本身的 RegEx 的组合来验证所有映射都符合您的要求。对我来说听起来很痛苦。

我想到的另一件事是在 DbModelBuilder 周围创建一个外观来拦截和检查通过它的所有内容,但我不知道这是否可以处理基于约定的东西。听起来也很痛苦。

作为一种不太完整但更简单的替代方法,您可以通过尽可能切换到基于属性的映射来消除其中的大部分。这将允许您创建一个基本测试类,例如ModelTesting,其中包括一些使用反射来验证TEntity是否具有以下功能的测试方法:

  • 单个表属性。
  • 每个属性都有一个 ColumnAttribute 或 NotMappedAttribute。
  • 至少一个具有键属性的属性。
  • 每个属性类型映射到兼容的数据库类型。

您甚至可以根据属性和类的名称强制实施命名约定(对于每个层次结构的表类型有一个警告)。也可以检查外键映射。这是一个一次性写入基类,您可以为每个模型类型派生一次并捕获大部分错误(好吧,无论如何,它捕获了我的大部分错误)。

任何不能用属性表示的东西,比如TPH继承等,都会变得更加困难。启动 DbContext 并在 Set() 上执行 FirstOrDefault 的集成测试可能会涵盖大多数这些基础,假设您的 DbContext 没有为您生成数据库。

如果你写了一个方法

public static string ToMappingString(this Widget obj)

然后,您可以通过批准测试(www.approvaltests.com 或nuget)轻松测试这一点

这里有一个视频:http://www.youtube.com/watch?v=vKLUycNLhgc

但是,如果您想测试"我的对象保存并检索自己"那么这是"基于理论的测试"的完美场所

基于理论的测试大多数单元测试采用以下形式:

Given A,B expect C

基于理论的测试是

Given A,B expect Theory

这样做的美妙之处在于,无需担心A和B采用哪种特定形式,因为您不需要知道C,因此任何随机生成器都可以工作。

示例 1:测试加减法

通常你会有这样的东西

Assert.AreEqual(5, Add(2,3));
Assert.AreEqual(9, Add(10,-1));
Assert.AreEqual(10, Add(5,5));
Assert.AreEqual(7, Subtract(10,3));

但是,如果您编写理论测试,它看起来像

for(int i = 1; i < 100; i++)
{ 
    int a = random.Next();
    int b = random.Next();
    Assert.AreEqual(a, Subtract(Add(a,b),b, string.Format("Failed for [a,b] = [{0},{1}], a,b));        
}

现在您已经了解了基于理论的测试,您尝试测试的理论是

Given Model A
When A is stored to the database, and retrieved the resulting object is equal to A