草庐IT

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

codeneng 2023-03-28 原文

Integration testing database, am I doing it right?

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    [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 测试...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 框架来编写我们的测试,并利用它的能力在每次测试之前和之后,或者在每组测试之前和之后执行逻辑。下面的代码适用于只执行选择查询的测试。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    [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.
        }
    }

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    [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,你应该做类似...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     [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#:集成测试数据库,我做对了吗?的更多相关文章

    1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

      我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

    4. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

      我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

    5. ruby - RSpec - 使用测试替身作为 block 参数 - 2

      我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

    6. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

      Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

    7. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

      我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

    8. ruby - 即使失败也继续进行多主机测试 - 2

      我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

    9. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

      我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

    10. ruby - Ruby 有 `Pair` 数据类型吗? - 2

      有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

    随机推荐