关于c#:集成测试数据库,我做对了吗?

Integration testing database, am I doing it right?

我想在我的 MVC4 应用程序中测试依赖并使用数据库的方法。我不想使用模拟方法/对象,因为查询可能很复杂,并且为此创建测试对象太费力了。

我发现了集成测试的想法,它将测试的数据库操作逻辑package在一个 TransactionScope 对象中,该对象在完成时回滚更改。

不幸的是,这首先不是从一个空数据库开始,它还使主键依赖(即,当数据库中已经有一些项目具有主键 1 和 2 时,然后在我运行测试之后依靠 4),我不想要这个。

这是我想出的"集成测试",只是为了测试是否实际添加了产品(例如,我想创建更困难的测试,在我拥有正确的基础架构后检查方法)。

    [TestMethod]
    public void ProductTest()
    {
        // Arrange
        using (new TransactionScope())
        {
            myContext db = new myContext();
            Product testProduct = new Product
            {
                ProductId = 999999,
                CategoryId = 3,
                ShopId = 2,
                Price = 1.00M,
                Name ="Test Product",
                Visible = true
            };

            // Act
            db.Products.Add(testProduct);
            db.SaveChanges();

            // Assert
            Assert.AreEqual(1, db.Products.ToList().Count());
            // Fails since there are already items in database

        }

    }

这引发了很多问题,这里有一个选择:如何从一个空数据库开始?我应该使用自己的上下文和连接字符串将另一个数据库附加到项目吗?最重要的是,如何在不破坏旧数据的情况下正确地在实际数据库上测试方法?

我整天都在忙于弄清楚如何对我的数据库逻辑进行单元/集成测试。希望这里有经验的开发者可以提供一些帮助!

/edit 确实会影响/更改我的数据库的 NDbUnit 测试...

public class IntegrationTests
{
    [TestMethod]
    public void Test()
    {
        string connectionString ="Data Source=(LocalDb)\\\\v11.0;Initial Catalog=Database_Nieuw;
            Integrated Security=false;"
;
        //The above is the only connectionstring that works... And is the"real" local database
        //This is not used on Jenkins but I can perhaps attach it???
        NDbUnit.Core.INDbUnitTest mySqlDatabase = new
        NDbUnit.Core.SqlClient.SqlDbUnitTest(connectionString);
        mySqlDatabase.ReadXmlSchema(@"..\\..\
DbUnitTestDatabase\
DbUnitTestDatabase.xsd"
);
        mySqlDatabase.ReadXml(@"..\\..\
DbUnitTestDatabase\\DatabaseSeeding.xml"
); // The data
        mySqlDatabase.PerformDbOperation(NDbUnit.Core.DbOperationFlag.CleanInsertIdentity);
}

I do not want to use mock methods / objects because the queries can be complicated and creating test objects for that is too much of an effort.

这是正确的策略。大多数"有趣"的错误往往发生在客户端代码和(真实)数据库之间的"边界"。

How can I start with an empty database?

在每次测试之前以编程方式清除数据库。您可以通过将清除代码放在标有 [TestInitialize] 属性的方法中来自动执行此操作。如果您的数据库碰巧使用了 ON DELETE CASCADE,那么删除所有数据可能就像删除几个"顶级"表一样简单。

或者,只需编写具有弹性的测试,以防数据库中已经有一些数据。例如,每个测试都将生成自己的测试数据并仅使用生成数据的特定 ID。这可以让您获得更好的性能,因为您不需要运行任何额外的清除代码。

And most importantly, how do I properly test methods on an actual database without ruining my old data?

算了。除了可以根据需要丢弃的开发数据库之外,切勿在任何东西上运行此类测试。迟早你会提交一些你不打算做的事情,或者持有比生产中可接受的更长时间的锁(例如,通过在调试器中命中断点),或者以不兼容的方式修改模式,或者只是用负载测试来锤击它否则会影响真实用户的工作效率...

相关讨论

  • 所以这也意味着我应该使用一个额外的数据库。我在一本关于持续集成的书中读到的 NDbUnit 怎么样?
  • @user2609980 抱歉,我没有使用 NDbUnit 的经验。
  • @user2609980 NDbUnit 是一个合适的替代方案,因为它允许您在 xml 文件中定义测试数据 - 参见例如codeproject.com/Articles/529830/…。但是请注意,当您频繁更改架构时,它会变得非常麻烦。
  • @ThomasWeller 谢谢。从现在开始,数据库几乎是不可更改的,所以这不是问题。我对 NDbUnit 不了解的是它是如何工作的。当我向数据库添加一些内容然后运行测试(它确实具有与"原始"数据库相同的连接字符串......)时,数据库被清除......我应该创建一个数据库的新实例在同一台服务器上使用另一个名称?
  • @user2609980 使用 NDbUnit 时,您根本没有"真实"数据库,而是一组驻留在测试项目中的本地 xml 文件。基于这些,您可以在每个测试的基础上将数据库设置为您需要的状态(空或某些数据等......)。浏览示例项目比任何解释都更好地证明了这一点......
  • @ThomasWeller 好吧,为什么当我从 NDbUnit 运行测试并在我的数据库中查看数据时会受到影响?这是因为使用了错误的连接字符串吗?我对真实数据库和虚假数据库使用相同的连接字符串。有关我正在使用的测试方法,请参阅我的 /edit 帖子。我会很感激你的意见。
  • @user2609980 啊!我之前的回答部分错误。对不起(我很着急)!当然,您确实需要一个数据库 - 只是它应该是您的实时数据库的副本,然后将您的连接字符串指向它(您可以轻松地使用免费的 SQL Server Express)。哦,然后你可以完全放弃所有交易(这对性能有很大帮助)。
  • @ThomasWeller 感谢您的回复。我如何创建一个新的数据库,我应该把它放在哪里?我在本地有一个 sql 服务器,但我也想要一个在测试环境(Jenkins)中。我不知道如何做到这一点。

我发现自己需要编写集成测试,但我没有针对开发数据库执行测试,因为它是一个变化的主题。由于我们在持续两周的 sprint 中使用了 scrum 方法,因此我们能够采用以下方法:

  • 在每个 sprint 结束时,我们将创建与开发数据库模式匹配的测试数据库。在大多数情况下,该数据库将在每次测试执行之前在测试数据库服务器上恢复,并在测试完成后将其删除。
  • 用可预测的数据集填充测试数据库,这不会是更改的主题,除了需要更改数据的测试。
  • 配置测试项目以针对测试数据库执行。
  • 我们编写的测试分为两部分。

  • 仅对数据库执行选择查询的测试。
  • 对数据库执行插入、更新、删除查询的测试。
  • 上述方法让我们始终知道每次测试执行后会发生什么。我们使用 MSTest 框架来编写我们的测试,并利用它的能力在每次测试之前和之后,或者在每组测试之前和之后执行逻辑。下面的代码适用于只执行选择查询的测试。

    [TestClass]
    public class Tests_That_Perform_Only_Select  
    {
        [ClassInitialize]
        public static void MyClassInitialize()
        {
            //Here would go the code to restore the test database.
        }

        [TestMethod]
        public void Test1()
        {
            //Perform logic for retrieving some result set.
            //Make assertions.
        }

        [TestMethod]
        public void Test2()
        {
            //Perform logic for retrieving some result set.
            //Make assertions.
        }

        [ClassCleanup]
        public static void MyClassCleanup()
        {
            //Here would go logic to drop the database.
        }
    }

    这样,测试将针对可预测的数据集执行,我们始终知道会发生什么。每个测试类将执行一次数据库的恢复和删除,这将加快测试的执行。

    对于在数据库中执行更改的测试,在每个测试执行之前恢复和删除数据库是强制性的,因为我们不希望我们的下一个测试针对具有未知状态的数据库执行,因为我们不会知道会发生什么。这是该场景的代码示例:

    [TestClass]
    public class Tests_That_Perform_Insert_Update_Or_Delete
    {
        [TestInitialize]
        public void MyTestInitialize()
        {
            //Here would go the code to restore the test database.
        }

        [TestMethod]
        public void Test1()
        {
            //Perform logic.
            //Make assertions.
        }

        [TestMethod]
        public void Test2()
        {
            //Perform some logic.
            //Make assertions.
        }

        [TestCleanup]
        public void MyClassCleanup()
        {
            //Here would go logic to drop the database.
        }
    }

    在这种情况下,测试数据库在每次测试之前和之后都会恢复和删除。

    相关讨论

    • 谢谢你的评论。这确实意味着我应该创建一个额外的测试数据库。不知道如何将它与运行 Jenkins 的构建服务器结合起来。
    • 在构建服务器上,我们使用 MSTest 配置了测试的执行。恢复和删除数据库的逻辑是执行两个 sql 脚本,它们是测试项目的一部分。这些脚本在测试数据库备份所在的测试数据库服务器上执行。我们使用 Hudson CI 而不是 Jenkins,但这并不重要。
    • 你把你的数据库放在哪里了?
    • 测试数据库的备份位于测试数据库服务器上。测试由 Hudson CI Server 执行。测试项目包含带有指向数据库服务器的连接字符串的 App.Config 文件。在 ClassInitialize/TestInitialize 上执行的脚本将备份还原到测试数据库服务器。然后针对测试数据库执行实际测试。之后在 ClassCleanup/TestCleanup 上,执行删除测试数据库的脚本。

    您应该检查您的函数创建的特定案例。将断言视为您在此测试中专门检查的内容。现在,您的测试正在检查,数据库中是否正好有 1 条记录。而已。更有可能的是,您希望您的断言意味着,A)我实际上只是将一个项目添加到数据库中吗?或者,B)我是否只是将刚??刚创建的特定项目添加到数据库中。

    对于 A,你应该做类似...

     [TestMethod]
        public void ProductTest()
        {
            // Arrange
            using (new TransactionScope())
            {
                myContext db = new myContext();
                var originalCount = db.Products.ToList().Count();

                Product testProduct = new Product
                {
                    ProductId = 999999,
                    CategoryId = 3,
                    ShopId = 2,
                    Price = 1.00M,
                    Name ="Test Product",
                    Visible = true
                };

                // Act
                db.Products.Add(testProduct);
                db.SaveChanges();

                // Assert
                Assert.AreEqual(originalCount + 1, db.Products.ToList().Count());
                // Fails since there are already items in database

            }

        }

    对于 B),我会让你自己弄清楚,但实际上,你应该检查分配给你的对象的特定 ID。

    相关讨论

    • 谢谢。不过,这仍会继续向主键添加值。我会选择 NdbUnit 或 Test 数据库。

    以上是关于c#:集成测试数据库,我做对了吗?的全部内容。
    THE END
    分享
    二维码
    < <上一篇
    )">
    下一篇>>