单元测试数据库

单元测试数据库

Unit-Testing Databases

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

  • 无状态的
  • 彼此独立
  • 具有相同结果的可重复性,即没有持久的更改

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

我最终的解决方案涉及非常大的设置功能(糟糕!)和一个空的测试用例,该用例将首先运行并指示安装程序没有问题。这在维持测试无状态的同时牺牲了测试的独立性。

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

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

tgmdbm说:

You typically use your favourite
automated unit testing framework to
perform integration tests, which is
why some people get confused, but they
don't follow the same rules. You are
allowed to involve the concrete
implementation of many of your classes
(because they've been unit tested).
You are testing how your concrete
classes interact with each other and
with the database.

因此,如果我正确阅读了这篇文章,实际上就没有办法有效地对数据访问层进行单元测试。或者,数据访问层的"单元测试"是否涉及测试,例如由类生成的SQL /命令,而与与数据库的实际交互无关?


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

您通常不对数据库进行单元测试。您通常将数据库包含在集成测试中。

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


数据库单元

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


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

犀牛Mo是有很好声誉的人。

NMock是另一个。

也有很多商业模拟库可用。编写好的单元测试的一部分实际上是在为它们设计代码-例如,通过使用有意义的接口,以便您可以通过实现其接口的"伪造"版本来实现"模拟"相关对象,而该接口的行为仍在可预测的方式,用于测试目的。

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

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


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

我强烈建议Moq作为您的模拟框架。我用过Rhino Mocks和NMock。 Moq非常简单,解决了我在其他框架中遇到的所有问题。


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

基本思想是在DAL中使用每种方法-声明结果-并在完成每个测试后回滚以便数据库干净(没有垃圾/测试数据)。

您可能找不到的"唯一"问题是,我通常会进行整个CRUD测试(并非纯粹从单元测试的角度来看),但是此集成测试使您可以看到实际的CRUD +映射代码。这样,如果发生故障,您将在启动应用程序之前就知道(当我尝试快速运行时,为我节省了大量工作)


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

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

单元测试模式


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

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
    [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


推荐阅读