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

时间:2011-01-13 03:16:08

标签: c# unit-testing entity-framework-4 functional-testing sql-server-ce

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

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

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

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

我尝试将TestInitializeTestCleanup代码更改为我的测试类ClassInitializeClassCleanup,但由于测试而导致NullReferenceException出错在ClassInitialize之前运行(或者它出现。ClassInitialize在基类中,所以可能导致它。)

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

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

进行有效的单元测试

<小时/> 最终编辑:在与Microsoft之间来回发送一些电子邮件之后,事实证明现在在SqlCE中允许TransactionScope使用最新版本的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!
        }
    }

2 个答案:

答案 0 :(得分:4)

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();
    }
}

然后在您的初始化中,您将把呼叫更改为:

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

答案 1 :(得分:3)

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

与此同时,我更进一步,使其在我的代码中变得更加清洁。将此块添加到您的数据上下文(无论您从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不直接支持它。