【问题标题】:How can I unit test Entity Framework Code First Mappings?如何对实体框架代码优先映射进行单元测试?
【发布时间】:2012-04-25 20:49:27
【问题描述】:

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

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

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

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

【问题讨论】:

  • 好吧,我不喜欢 EF,如果可能的话,评估一下使用 NHibernate 的可能性,使用 NHibernate 你可以很容易地测试你的映射
  • @Jupaol 很高兴知道,我们同时使用 EF 和 NH,两者都有问题

标签: c# unit-testing ef-code-first entity-framework-4.3


【解决方案1】:

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

例如:

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

将导致 SQL Server Provider 出现这种情况:

"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 Parsers,所以这个解决方案更通用,显然 .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/

【讨论】:

    【解决方案2】:

    我能想到的涵盖所有可能选项的唯一方法是使用 Entity Framework Power Tools 来预编译 DbContext 的视图,并且可能结合使用对生成的类型的反射和生成的 RegEx代码本身以验证所有内容都按照您想要的方式进行映射。对我来说听起来很痛苦。

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

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

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

    您甚至可以根据属性和类的名称强制执行命名约定(需要注意每个层次结构的表类型)。也可以检查外键映射。这是一个只写一次的基类,你可以为你的每种模型类型派生一次,并捕捉你的大部分错误(好吧,它捕捉到了我的大部分错误)。

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

    【讨论】:

    • 感谢肖恩的输入!我正在考虑不太全面的方法,例如使用单一配置方法,并将查看 EF 电动工具,我不知道它们。
    【解决方案3】:

    如果你写了一个方法

    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
    

    【讨论】:

      猜你喜欢
      • 2011-04-21
      • 2011-08-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-12
      相关资源
      最近更新 更多