集成测试数据库,我做得对吗?

时间:2013-12-02 17:50:02

标签: c# unit-testing asp.net-mvc-4 integration-testing

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

我发现集成测试的想法将测试的数据库操作逻辑包装在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(@"..\..\NDbUnitTestDatabase\NDbUnitTestDatabase.xsd");
        mySqlDatabase.ReadXml(@"..\..\NDbUnitTestDatabase\DatabaseSeeding.xml"); // The data
        mySqlDatabase.PerformDbOperation(NDbUnit.Core.DbOperationFlag.CleanInsertIdentity);
}

3 个答案:

答案 0 :(得分:13)

  

我不想使用模拟方法/对象,因为查询可能很复杂,为此创建测试对象太费劲了。

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

  

如何从空数据库开始?

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

或者,只需将测试编写为具有弹性,以防数据库中已存在某些数据。例如,每个测试都会生成自己的测试数据,并使用生成的数据的特定ID。这样可以提高性能,因为您不需要运行任何额外的清除代码。

  

最重要的是,如何在不破坏旧数据的情况下在实际数据库上正确测试方法?

忘了它。除了可以根据需要丢弃的开发数据库之外,不要对任何东西运行此类测试。迟早你会提交你不想要的东西,或者在生产中持有一些比生产中可接受的更长的锁(例如通过在调试器中命中断点),或者以不兼容的方式修改模式,或者只是用负载测试来敲定它。否则会影响真实用户的生产力......

答案 1 :(得分:3)

我发现自己处于编写集成测试的情况,但我没有对开发数据库执行测试,因为它是一个变化的主题。由于我们使用scrum方法进行了持续两周的冲刺,我们采用了以下方法:

  1. 在每个sprint结束时,我们将生成与开发数据库的模式匹配的测试数据库。在大多数情况下,在执行每个测试之前,将在测试数据库服务器上恢复此数据库,并在测试完成后将其删除。
  2. 使用可预测的数据集填充测试数据库,除了需要更改数据的测试外,这些数据不会发生变化。
  3. 配置测试项目以针对测试数据库执行。
  4. 我们编写的测试分为两部分。

    1. 仅针对数据库执行选择查询的测试。
    2. 对数据库执行插入,更新,删除查询的测试。
    3. 上述方法使我们始终知道每次测试执行后会发生什么。我们使用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.
          }
      }
      

      在此方案中,将在每次测试之前和之后恢复和删除测试数据库。

答案 2 :(得分:2)

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

对于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。