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

我想测试依赖于数据库并使用数据库的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 } } 

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

我整天忙着试图弄清楚如何对我的数据库逻辑进行单元/集成测试。 我希望这里有经验丰富的开发人员能提供一些帮助

/编辑影响/更改我的数据库的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); } 

我不想使用模拟方法/对象,因为查询可能很复杂,并且为此创建测试对象需要付出太多努力。

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

如何从空数据库开始?

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

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

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

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

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

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

我们编写的测试分为两部分。

  1. 仅对数据库执行选择查询的测试。
  2. 对数据库执行插入,更新,删除查询的测试。

上述方法使我们始终知道每次测试执行后会发生什么。 我们使用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. } } 

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

您应该检查由您的函数创建的特定案例。 将断言想象为您在此测试中专门检查的内容。 现在,您的测试正在检查,数据库中是否有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。