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

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

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

我的最终解决方案涉及非常大的设置函数(哎呀!)和一个空的测试用例,该测试用例将首先运行并表明设置运行没有问题。这是牺牲测试的独立性,同时保持其无状态性。

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

那么,面对这种情况,SO社区做了什么?


tgmdbm 说道:

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

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

有帮助吗?

解决方案

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

你通常不会 单元 测试数据库。您通常会涉及到数据库 一体化 测试。

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

其他提示

数据库单元

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

单元测试中外部依赖关系的通常解决方案是使用模拟对象 - 也就是说,模拟您正在测试的真实对象的行为的库。这并不总是那么简单,有时需要一些独创性,但是如果您不想“自己动手”,那么有几个很好的(免费软件).Net 模拟库。我立刻想到两个:

犀牛模拟 是一个有很好声誉的人。

NMock 是另一个。

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

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

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

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

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

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

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

单元测试模式

我解释了我在这种情况下一直使用的一种技术 这里.

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

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

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

测试数据层和数据库在一起,几乎没有任何惊喜。但是,针对数据库的测试存在问题,主要是您正在针对许多测试共享的状态进行测试。如果在一个测试中将一行插入数据库中,则下一个测试也可以看到该行。
您需要的是一种回滚对数据库所做的更改的方法。
交易范围 课程足够聪明,可以处理非常复杂的交易以及嵌套交易,其中您的测试呼叫代码在其本地交易中提交。这是一个简单的代码,它显示了在测试中添加回滚能力的容易性:

    [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);
        }
    }

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top