【问题标题】:How can I use Sql CE 4 databases for functional tests如何使用 Sql CE 4 数据库进行功能测试
【发布时间】: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 测试它

标签: c# unit-testing entity-framework-4 functional-testing sql-server-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 将允许我轻松完成或回滚事务。到目前为止,这似乎也有效。希望将来我可以自己使用事务范围。
猜你喜欢
  • 2014-08-02
  • 2012-10-09
  • 1970-01-01
  • 2017-09-23
  • 1970-01-01
  • 2015-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多