65

我想用命中数据库的 NUnit 编写单元测试。我希望每个测试的数据库都处于一致状态。我认为交易可以让我“撤消”每个测试,所以我四处搜索并找到了 2004-05 年关于该主题的几篇文章:

这些似乎解决了为 NUnit 实现自定义属性的问题,该属性构建了在每次测试执行后回滚数据库操作的能力。

这很棒但是...

  1. 这个功能是否存在于 NUnit 的某个地方?
  2. 在过去的 4 年中,这项技术是否得到了改进?
  3. 这仍然是测试数据库相关代码的最佳方法吗?

编辑:这不是我想专门测试我的 DAL,我更想测试与数据库交互的代码片段。对于这些测试是“无接触”和可重复的,如果我可以在每次测试后重置数据库,那就太棒了。

此外,我想将其简化为目前没有测试场所的现有项目。出于这个原因,我实际上无法为每个测试从头开始编写数据库和数据的脚本。

4

6 回答 6

79

NUnit 现在有一个 [Rollback] 属性,但我更喜欢用不同的方式来做。我使用TransactionScope类。有几种使用它的方法。

[Test]
public void YourTest() 
{
    using (TransactionScope scope = new TransactionScope())
    {
        // your test code here
    }
}

由于您没有告诉 TransactionScope 提交它会自动回滚。即使断言失败或引发其他异常,它也可以工作。

另一种方法是使用 [SetUp] 创建 TransactionScope 并使用 [TearDown] 对其调用 Dispose。它减少了一些代码重复,但完成了同样的事情。

[TestFixture]
public class YourFixture
{
    private TransactionScope scope;

    [SetUp]
    public void SetUp()
    {
        scope = new TransactionScope();
    }

    [TearDown]
    public void TearDown()
    {
        scope.Dispose();
    }


    [Test]
    public void YourTest() 
    {
        // your test code here
    }
}

这与单个测试中的 using 语句一样安全,因为 NUnit 将保证调用 TearDown。

说了这么多,我确实认为命中数据库的测试并不是真正的单元测试。我仍然编写它们,但我认为它们是集成测试。我仍然认为它们提供价值。我经常使用它们的一个地方是测试 LINQ to SQL 代码。我不使用设计师。我手写了 DTO 和属性。众所周知,我弄错了。集成测试有助于发现我的错误。

于 2008-12-15T01:55:25.523 回答
3

我刚去了一个 .NET 用户组,演示者说他在测试设置和拆卸中使用了 SQLlite,并使用了内存选项。他不得不稍微捏造连接并明确破坏连接,但每次都会提供一个干净的数据库。

http://houseofbilz.com/archive/2008/11/14/update-for-the-activerecord-quotmockquot-framework.aspx

于 2008-11-26T16:07:32.940 回答
2

我会称这些集成测试,但没关系。我为此类测试所做的是让测试类中的设置方法在每次测试之前清除所有感兴趣的表。我通常手动编写 SQL 来执行此操作,这样我就不会使用被测类。

通常,我的数据层依赖于 ORM,因此我不会在那里编写太多单元测试。我觉得不需要对我不写的代码进行单元测试。对于我在层中添加的代码,我通常使用依赖注入来抽象出与数据库的实际连接,这样当我测试我的代码时,它就不会触及实际的数据库。结合模拟框架执行此操作以获得最佳结果。

于 2008-11-26T16:06:51.020 回答
0

对于这种测试,我尝试了 NDbUnit(与 NUnit 协同工作)。如果没记错的话,它是来自 Java 平台的 DbUnit 的一个端口。它有很多巧妙的命令来完成你想要做的事情。该项目似乎已移至此处:

http://code.google.com/p/ndbunit/

(它曾经在http://ndbunit.org)。

来源似乎可通过此链接获得:http: //ndbunit.googlecode.com/svn/trunk/

于 2008-11-26T16:09:42.420 回答
0

考虑创建一个数据库脚本,以便您可以从 NUnit 自动运行它,也可以手动运行它以进行其他类型的测试。例如,如果使用 Oracle,则从 NUnit 中启动 SqlPlus 并运行脚本。这些脚本通常写起来更快,更容易阅读。此外,非常重要的是,从 Toad 或等效程序运行 SQL 比从代码运行 SQL 或从代码通过 ORM 更具启发性。通常我会创建一个 setup 和 teardown 脚本并将它们放入 setup 和 teardown 方法中。

您是否应该从单元测试中通过数据库是另一个讨论。我相信这样做通常是有意义的。对于许多应用程序来说,数据库是绝对的行动中心,逻辑是高度基于集合的,所有其他技术、语言和技术都是过客。随着函数式语言的兴起,我们开始意识到 SQL 和 JavaScript 一样,实际上是一种伟大的语言,这些年来就在我们的眼皮底下。

顺便说一句,Linq to SQL(我在概念上喜欢它,但从未使用过)在我看来几乎是一种从代码中执行原始 SQL 而不承认我们在做什么的方式。有些人喜欢 SQL 并且知道他们喜欢它,有些人喜欢它但不知道他们喜欢它。:)

于 2010-05-07T18:04:02.260 回答
0

对于像我这样最近来到这个线程的任何人,我想建议尝试我目前正在为这个特定案例开发的Reseed库。

内存数据库替换(缺少功能)和事务回滚(事务不能嵌套)对我来说都不是一个合适的选择,所以我最终选择了一个简单的删除/插入循环来恢复数据。最后用一个库来生成这些,同时尝试优化我的测试速度和设置的简单性。如果它帮助其他人会很高兴。

我推荐的另一种选择是使用数据库快照来恢复数据,它具有相当的性能和可用性。工作流程如下:

  • 删除现有快照;
  • 创建数据库;
  • 插入数据;
  • 创建快照;
  • 执行测试;
  • 从快照恢复;
  • 去“执行测试”,直到没有人离开;
  • 删除快照。

如果您可以拥有所有测试的唯一数据脚本并允许您在唯一的时间执行插入(这应该是最慢的),那么它是合适的,而且您根本不需要数据清理脚本。

为了进一步提高性能,因为此类测试可能需要大量时间,请考虑使用数据库池和测试并行化。

于 2021-11-13T21:58:08.690 回答