如何使用 Sql CE 4 数据库进行功能测试

Posted

技术标签:

【中文标题】如何使用 Sql CE 4 数据库进行功能测试【英文标题】:How can I use Sql CE 4 databases for functional tests 【发布时间】:2011-01-13 03:16:07 【问题描述】:

由于 Linq-to-Entities (EF4) 和 Linq-to-Objects 之间的潜在差异,我需要使用实际的数据库来确保我的查询类正确地从 EF 检索数据。 Sql CE 4 似乎是解决此问题的完美工具,但我遇到了一些小问题。这些测试正在使用 MsTest。

我遇到的问题是,如果没有重新创建数据库(由于模型更改),每次测试后数据都会不断添加到数据库中,而不会删除数据。这可能会导致测试冲突,查询返回的数据比预期的要多。

我的第一个想法是在TestInitialize方法中初始化一个TransactionScope,并在TestCleanup中处理事务。不幸的是,Sql CE4 不支持事务。

我的下一个想法是通过File.Delete() 调用删除TestCleanup 中的数据库。不幸的是,这似乎在第一次测试运行后不起作用,因为第一次测试的TestCleanup 似乎删除了数据库,但第一次测试之后的每个测试似乎都没有重新创建数据库,因此它给出了一个错误找不到数据库文件。

我尝试将 TestInitializeTestCleanup 标记更改为 ClassInitializeClassCleanup 用于我的测试课程,但是由于在 ClassInitialize 之前运行的测试导致 NullReferenceException 出错(或者这样出现。ClassInitialize 在基类中,所以可能是它的原因)。

我已经没有办法有效地使用 Sql CE4 进行测试。有没有人有更好的想法?


编辑:我最终找到了解决方案。在我的 EF 单元测试基类中,我启动了我的数据上下文的一个新实例,然后调用 context.Database.Delete()context.Database.Create()。单元测试运行速度稍慢,但现在我可以使用真实数据库有效地进行单元测试


最终编辑: 在与 Microsoft 来回发送一些电子邮件后,发现 TransactionScopes 现在可以在 SqlCE 中使用最新版本的 SqlCE。但是,如果您使用的是 EF4,则存在一些限制,即您必须在开始事务之前显式打开数据库连接。以下代码显示了如何成功使用 Sql CE 进行单元/功能测试的示例:
    [TestMethod]
    public void My_SqlCeScenario ()
    
        using (var context = new mysqlCeModelContext()) //ß derived from DbContext
        
            ObjectContext objctx = ((IObjectContextAdapter)context).ObjectContext;
            objctx.Connection.Open(); //ß Open your connection explicitly
            using (TransactionScope tx = new TransactionScope())
            

                var product = new Product()  Name = "Vegemite" ;
                context.Products.Add(product);
                context.SaveChanges();
            
            objctx.Connection.Close(); //ß close it when done!
        
    

【问题讨论】:

当然 SQL CE 支持事务...但是使用 TransactionScope 是一种非常错误的做法。只需通过 Connection 对象正常执行即可。 我不确定在没有TransactionScope 的情况下如何处理 EF4 实体,除非您的意思是不调用 SaveChanges(),这意味着测试不是有效的测试。 您能否举例说明如何使用 Sql CE 播种数据?我使用 EF6 并想使用 sql ce 测试它 【参考方案1】:

在您的TestInitialize 中,您应该执行以下操作:

System.Data.Entity.Database.DbDatabase.SetInitializer<YourEntityFrameworkClass>(
    new System.Data.Entity.Database.DropCreateDatabaseAlways<YourEntityFrameworkClass>());

这将导致实体框架在运行测试时始终重新创建数据库。

顺便说一句,您可以创建一个继承自 DropCreateDatabaseAlways 的替代类。这将允许您每次都使用集合数据为您的数据库播种。

public class DataContextInitializer : DropCreateDatabaseAlways<YourEntityFrameworkClass> 
    protected override void Seed(DataContext context) 
        context.Users.Add(new User()  Name = "Test User 1", Email = "test@test.com" );
        context.SaveChanges();
    

然后在您的 Initialize 中,您将调用更改为:

System.Data.Entity.Database.DbDatabase.SetInitializer<YourEntityFrameworkClass>(
    new DataContextInitializer());

【讨论】:

在终于有机会检查这个之后,这似乎不起作用。它似乎在运行任何测试之前删除了数据库,但在单独运行每个测试之前它并没有删除数据库。这导致数据在单元测试中持续存在,导致其他一些测试失败。我现在可以解决它,但我宁愿没有它。 没关系 - 它太复杂了,不适合单元测试......我基本上用它来将多个表映射到一个实体,每次我调用它时它都会在一个新的程序集中完成对于那个特定的表......真的很难解释:) 您不能在每次测试后清除表格并使用种子方法重新填充它们吗?还是每个测试都是一组不同的表? 一些测试我正在测试用户查询,一些我正在测试项目查询等。所以每个测试都使用不同的数据,所以我必须手动编写命令来清除每个表,之后每个测试,随着我的应用程序的增长,这似乎是很多艰苦的工作和维护,但也许这是唯一的方法(尽管这迫使我在我的存储库中公开 deleteAll 方法)。当我开始编写更多基于聚合的测试时(例如,在所有值中搜索关键字),我可以看到自己被测试之间的持久数据烧毁了。 我将您的帖子标记为解决方案,让您代表回来并给我一些建议。根据您的建议,我找到了一个更可靠的解决方案,我把它放在原帖中!谢谢!【参考方案2】:

我发现“最终编辑”中的方法也适合我。然而,这真的很烦人。它不仅用于测试,还可以在任何时候将 TransactionScope 与 Entity Framework 和 SQL CE 一起使用。我想编写一次代码并让我的应用程序同时支持 SQL Server 和 SQL CE,但是在我使用事务的任何地方我都必须这样做。当然,Entity Framework 团队应该为我们处理这个问题!

与此同时,我更进一步,使其代码更简洁。将此块添加到您的数据上下文(无论您从 DbContext 派生的什么类):

public MyDataContext()

    this.Connection.Open();


protected override void Dispose(bool disposing)

    if (this.Connection.State == ConnectionState.Open)
        this.Connection.Close();

    base.Dispose(disposing);


private DbConnection Connection

    get
    
        var objectContextAdapter = (IObjectContextAdapter) this;
        return objectContextAdapter.ObjectContext.Connection;
    

这会让你在实际使用时更干净:

using (var db = new MyDataContext())

    using (var ts = new TransactionScope())
    
        // whatever you need to do

        db.SaveChanges();
        ts.Complete();
    

虽然我认为如果您设计您的应用程序,以便在一次调用 SaveChanges() 中提交所有更改,那么隐式事务就足够了。对于测试场景,我们希望回滚所有内容,而不是调用 ts.Complete(),所以那里肯定需要它。我确信在其他情况下我们需要可用的事务范围。很遗憾,EF/SQLCE 不直接支持它。

【讨论】:

有趣的解决方案。我刚刚做了一个解决方法,我的IUnitOfWork 类有一个BeginTransaction()EndTransaction(bool commit) 方法。 BeginTransaction 将打开连接,如果它还不存在,EndTransaction 将允许我轻松完成或回滚事务。到目前为止,这似乎也有效。希望将来我可以自己使用事务范围。

以上是关于如何使用 Sql CE 4 数据库进行功能测试的主要内容,如果未能解决你的问题,请参考以下文章

SQL CE 和 SQLite数据库对比测试

使用 SQL Server CE 4.0 的简单实体查询慢

在 SQL Server CE 4.0 中使用 COUNT 进行比较

使用 Effort 和 SQL CE 并行进行单元测试失败

实体框架、SQL CE 4.0 和 DB 测试自动化

如何让实体数据模型与 SQL Server CE 4.0 一起使用?