32

去年夏天,我正在开发一个基本的 ASP.NET/SQL Server CRUD 应用程序,单元测试是其中一项要求。当我尝试对数据库进行测试时遇到了一些麻烦。据我了解,单元测试应该是:

  • 无国籍
  • 彼此独立
  • 可重复使用相同的结果,即没有持续的变化

在为数据库开发时,这些要求似乎相互矛盾。例如,如果不确保要插入的行不存在,我就无法测试 Insert(),因此我需要先调用 Delete()。但是,如果他们不在那里呢?然后我需要先调用 Exists() 函数。

我的最终解决方案涉及非常大的设置功能(糟糕!)和一个空的测试用例,它将首先运行并表明设置运行没有问题。这是牺牲了测试的独立性,同时保持了它们的无国籍状态。

我发现的另一个解决方案是将函数调用包装在可以轻松回滚的事务中,例如Roy Osherove 的 XtUnit。这项工作,但它涉及另一个库,另一个依赖项,对于手头的问题,它似乎有点过于繁重。

那么,SO 社区在遇到这种情况时做了什么?


tgmdbm 说:

您通常使用您最喜欢的自动化单元测试框架来执行集成测试,这就是为什么有些人会感到困惑,但他们不遵循相同的规则。您可以参与许多类的具体实现(因为它们已经过单元测试)。您正在测试您的具体类如何相互交互以及如何与数据库交互

所以如果我没看错的话,真的没有办法有效地对数据访问层进行单元测试。或者,数据访问层的“单元测试”是否涉及测试,例如,由类生成的 SQL/命令,独立于与数据库的实际交互?

4

9 回答 9

26

除了断言表存在、包含预期的列并具有适当的约束之外,没有真正的方法可以对数据库进行单元测试。但这通常不值得这样做。

您通常不会对数据库进行单元测试。您通常会在集成测试中涉及数据库。

您通常使用您最喜欢的自动化单元测试框架来执行集成测试,这就是为什么有些人会感到困惑,但他们不遵循相同的规则。您可以参与许多类的具体实现(因为它们已经过单元测试)。您正在测试您的具体类如何相互交互以及如何与数据库交互。

于 2008-08-22T01:43:32.523 回答
11

数据库单元

您可以使用此工具在给定时间导出数据库的状态,然后在进行单元测试时,可以在测试开始时自动回滚到其先前的状态。我们经常在我工作的地方使用它。

于 2008-08-22T01:44:22.193 回答
5

单元测试中外部依赖的通常解决方案是使用模拟对象 - 也就是说,模仿您正在测试的真实对象行为的库。这并不总是直截了当的,有时需要一些独创性,但如果您不想“自己动手”,有几个好的(免费软件)模拟库可用于 .Net。两个瞬间浮现在脑海:

Rhino Mocks是一个有相当好的声誉。

NMock是另一个。

也有很多商业模拟库可用。编写好的单元测试的一部分实际上是为它们设计你的代码——例如,通过使用有意义的接口,这样你就可以通过实现其接口的“假”版本来“模拟”一个依赖对象,但该接口的行为仍然是可预测的方式,用于测试目的。

在数据库模拟中,这意味着使用返回组合表、行或数据集对象的对象来“模拟”您自己的数据库访问层,以供您的单元测试处理。

在我工作的地方,我们通常从头开始制作自己的模拟库,但这并不意味着您必须这样做。

于 2008-08-22T01:46:32.907 回答
4

是的,您应该重构代码以访问访问数据库的存储库和服务,然后您可以模拟或存根这些对象,以便被测对象永远不会触及数据库。这比存储数据库的状态并在每次测试后重置它要快得多!

我强烈推荐Moq作为你的模拟框架。我用过 Rhino Mocks 和 NMock。Moq 非常简单,解决了我在使用其他框架时遇到的所有问题。

于 2008-08-22T01:52:49.397 回答
2

我有同样的问题,并得出与这里的其他回答者相同的基本结论:不要打扰对实际的数据库通信层进行单元测试,但如果你想对你的模型函数进行单元测试(以确保它们正在拉数据正确,正确格式化等),使用某种虚拟数据源和设置测试来验证正在检索的数据。

我也发现单元测试的基本定义不适合许多 Web 开发活动。但是这个页面描述了一些更“高级”的单元测试模型,并且可能有助于激发在各种情况下应用单元测试的一些想法:

单元测试模式

于 2009-02-04T16:36:00.147 回答
2

我在这里解释了我一直用于这种情况的一种技术。

基本思想是在你的 DAL 中使用每个方法 - 断言你的结果 - 当每个测试完成时,回滚以便你的数据库是干净的(没有垃圾/测试数据)。

您可能觉得“不太好”的唯一问题是我通常会进行整个 CRUD 测试(从单元测试的角度来看不是纯粹的),但是此集成测试允许您查看 CRUD + 映射代码的实际运行情况。这样,如果它坏了,你会在启动应用程序之前知道(当我试图快速运行时为我节省了大量的工作)

于 2009-02-04T16:46:37.100 回答
1

您应该做的是从您从脚本生成的数据库的空白副本中运行您的测试。您可以运行您的测试,然后分析数据以确保它在您的测试运行后准确无误。然后你只需删除数据库,因为它是一次性的。这一切都可以自动化,并且可以被认为是一个原子动作。

于 2008-08-22T01:46:36.633 回答
1

一起测试数据层和数据库给项目的后期带来了一些惊喜。但是针对数据库进行测试有其问题,主要问题是您正在针对许多测试共享的状态进行测试。如果您在一次测试中将一行插入数据库,则下一个测试也可以看到该行。
您需要的是一种回滚您对数据库所做更改的方法。TransactionScope类足够聪明,可以处理非常复杂的事务,以及嵌套事务,在这些事务中,您的被测代码调用其自己的本地事务提交
。这是一段简单的代码,它显示了向测试添加回滚功能是多么容易:

    [TestFixture]
    public class TrannsactionScopeTests
    {
        private TransactionScope trans = null;

        [SetUp]
        public void SetUp()
        {
            trans = new TransactionScope(TransactionScopeOption.Required);
        }

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

        [Test]
        public void TestServicedSameTransaction()
        {
            MySimpleClass c = new MySimpleClass();
            long id = c.InsertCategoryStandard("whatever");
            long id2 = c.InsertCategoryStandard("whatever");
            Console.WriteLine("Got id of " + id);
            Console.WriteLine("Got id of " + id2);
            Assert.AreNotEqual(id, id2);
        }
    }
于 2014-04-25T15:24:00.510 回答
0

如果您使用 LINQ to SQL 作为 ORM,那么您可以即时生成数据库(前提是您有足够的用于单元测试的帐户的访问权限)。见http://www.aaron-powell.com/blog.aspx?id=1125

于 2008-12-16T11:04:08.693 回答