我可以测试LINQ查询的构造而不实际执行它

时间:2012-07-30 06:45:00

标签: .net unit-testing linq-to-entities tdd

我听说在测试EF时,由于LINQ to Objects和LINQ to Entities提供程序之间的差异,您需要对实时数据库使用集成测试。

为什么我们不能在不实际执行的情况下对查询的构造进行单元测试?我原本以为你可以将你的IQueryable附加到LINQ to Entities提供程序以某种方式并确认SQL是否正确生成(使用ToTraceString这样不执行实际查询的东西)。

我想象沿着这些行的代码(这个查询在L2O中运行良好但在L2E中运行不正确):

    <Test()> _
    Public Sub Query_Should_Build_Against_L2E()
        Dim testQuery = From d In myDb
                        Where d.Status = CType(Status.Ready, Integer)

        testQuery.SetQueryProvider("L2E")

        Assert.DoesNotThrow(testQuery.ToTraceString())
    End Sub

修改

我尝试按如下方式实施GertArnold的解决方案:

Dim context As New Context("Data Source=fakedbserver;Initial Catalog=fakedb;Persist Security Info=True;")
Dim result = context.myTable.Where(Function(d) d.Status=True)

抛出ProviderIncompatibleException,并显示消息“从数据库获取提供程序信息时发生错误。这可能是由Entity Framework使用不正确的连接字符串引起的。检查内部异常以获取详细信息并确保连接字符串是正确的。“这是完整的例外链:

  

System.Data.ProviderIncompatibleException:从数据库获取提供程序信息时发生错误。这可能是由实体框架使用不正确的连接字符串引起的。检查内部异常以获取详细信息,并确保连接字符串正确。

     

System.Data.ProviderIncompatibleException:提供程序未返回ProviderManifestToken字符串。

     

System.InvalidOperationException:此操作需要连接到“master”数据库。无法创建与“主”数据库的连接,因为已打开原始数据库连接并且已从连接字符串中删除凭据。提供未打开的连接。

     

System.Data.SqlClient.SqlException:建立与SQL Server的连接时发生与网络相关或特定于实例的错误。服务器未找到或无法访问。验证实例名称是否正确,以及SQL Server是否配置为允许远程连接。 (提供者:命名管道提供程序,错误:40 - 无法打开与SQL Server的连接)

2 个答案:

答案 0 :(得分:2)

  

将您的IQueryable附加到LINQ to Entities提供程序

问题是:哪IQueryable?接口本身就没有了。使用EF,您正在处理实现IQueryable的类,但除此之外还要做更多。事实上,他们完全有能力与上下文和查询提供商合作,已经附加IQueryable.IQueryProvider是一个只读属性,因此它由工厂设置,用于创建特定的IQueryable实现。

因此,您的testQuery将是ObjectQuery,因为它来自myDb,而不是EF上下文。

  

为什么我们不能在不实际执行的情况下对查询的构造进行单元测试?

这不是你的实际问题吗?事实上,我甚至想知道你是否想要一个不同的查询提供者。我想你会希望EF查询提供程序这样做,否则仍然不能保证与EF一样工作。

无论如何,您可以在其连接字符串中创建一个带有假数据库名称的上下文(以确保它不连接),并检查((ObjectQuery)testQuery).ToTraceString()的有效性。只要你不迭代testQuery就行了。我可以想象,当在复杂的执行路径中组合查询时,这种测试有一些价值。 (但我宁愿避免这样做。)

答案 1 :(得分:0)

简短回答

我能够创建一个模拟DbContext,它“连接”到正确的EF提供者而没有任何实际的数据库连接。这可以保证正在测试的LINQ实际上可以在针对您的真实数据库的集成场景中工作。它目前仅针对MS SQL Server实现,但它应该很容易扩展到其他EF提供程序。我已经发布了下面的完整代码,我很高兴听到有关它的反馈。

编辑我将代码移到GPL CodePlex project

答案很长

在查看GertArnold的答案并重新思考整个问题(以及对来源的广泛反思)后,我想出了我认为可行的解决方案。问题是DbContext的默认配置是:

  1. 使用默认SqlConnectionFactory创建与nameOrConnectionString构造函数的DbContext属性所请求的数据库的连接
  2. 如果数据库不存在则创建数据库(因为它使用CreateDatabaseIfNotExists作为默认上下文初始化程序)
  3. 打开与数据库的连接以确认其可访问(使用DbConnection.OpenDbConnection.State
  4. 查询数据库以确认其中存在所请求的模型(使用System.Data.Entity.Internal.InternalContext.QueryForModel,然后在活动连接上创建命令以查询模型的数据库)
  5. 构建System.Data.Common.DbProviderServicesSystem.Data.Common.DbProviderManifest对象,这些对象共同支持将表达式转换为SQL
  6. 这些步骤中的前四个(需要实际的数据库连接)可以创建一个新的IDatabaseInitializer,它在InitializeDatabase方法中什么都不做,然后用{{1注册测试中的上下文类型}}

    另一方面,步骤5是我们需要一个真正的DbContext进行选择的原因。如果没有DbProviderServices和DbProviderManifest,EF将不知道如何翻译表达式。我能够使用反射来获取内部Database.SetInitializerSqlProviderManifest并将工作传递给它们(我甚至可以指定按年使用的SQL服务器版本。)