在数据库上运行测试的策略

时间:2011-04-20 15:36:18

标签: .net sql-server testing mstest

我已经开始研究一个包含1800多个功能/集成测试的现有项目。这些都是用MSTest编码的。

其中许多直接连接到SQL Server数据库。数据库由代码生成器生成,代码生成器在许多方面创建数据库。生成数据库很慢而且很麻烦。

这有如下问题:

  • 测试清理db,这意味着我们必须为测试维护一个单独的db,而另一个用于使用该应用程序。现在的过程是在运行测试和运行应用程序之间进行更改时更改数据库。
  • 每个分支都需要拥有自己的数据库,因为每个数据库中的数据库模型可能不同(这意味着每个分支有2个数据库,步骤1)
  • 这很慢
  • 必须安装SQL Server和db才能运行测试

我希望现有的测试不依赖于这样的安装,如果可能的话运行得更快,而不必通过代码生成器处理维护数据库,处理连接字符串等等。

我试图尽快实现这一点,因为重写测试不在预算中我已经引入了模拟来帮助新测试减少对数据库的依赖,我现在的问题是现有的测试。

我的第一个问题是将我们的基本单元测试类更改为连接到SQLite数据库,该数据库将由代码生成器创建,该代码生成器已生成主数据库而不是SQL Server数据库。然后可以删除SQLite并将其重新复制到每次运行之间的测试文件夹中。这本来很快,不需要2个SQL Server数据库,事实上如果只是运行测试,则不需要安装SQL Server。

我的问题是生成的代码使用了SQLite中未包含的许多概念; T-SQL,SQL Server特定语法,模式,存储过程和嵌入式clr程序集。

然后我尝试了SQL Server CE 4,它有许多与SQLite相同的限制。

除了重写代码以与SQLite(或CE)兼容,重写现有测试或我们维护2 seperatedb的系统之外,还有其他可用的替代方案吗?

编辑:将单元测试更改为功能测试,澄清了一些事情。把一些东西放大胆。 我同意这些测试不是适当的单元测试。我同意嘲笑在这里会很好。我要做的是尝试解决我面临的混乱。

6 个答案:

答案 0 :(得分:7)

如果要连接到数据库或任何其他资源,则单元测试。适当的单元测试被隔离到被测单元或您的班级。正在测试的单位之外的任何东西都应该被嘲笑。

我不是.NET开发人员所以我不推荐最好的Mocking框架/工具,但也许this question will point you in the right direction

为什么单元测试不应该访问资源:

你会想要以不同的方式看待这个问题。您没有任何测试数据库本身的愿望,因为您必须假设您正在使用的数据库产品正常工作。因此,没有必要测试“$ dao-> save()”实际上是否插入了一条记录。您可能感兴趣的是在完成某些其他操作时是否正在调用save()方法,或者您的DAO是否正在生成正确的INSERT语句。

如果您必须在单元测试中进行数据库调用,因为您无法模拟进行数据库调用的对象,则需要重构。

肥皂盒时间:

这就是测试驱动开发如此有益的原因。从一开始就强调保持脱钩和隔离,从而带来更好的整体设计,灵活性和可测试性。

答案 1 :(得分:7)

您可以在每次测试之前创建数据库快照,然后再恢复数据库。创建和恢复快照比完全备份/恢复或重建数据库更轻松

 CREATE DATABASE myDb_snapshot ON
    ( NAME = myDb_snapshot, FILENAME = 'C:\MSSQL\Data\myDb_snapshot.ss' )
 AS SNAPSHOT OF myDb;

在测试执行后恢复:

 RESTORE DATABASE myDb FROM DATABASE_SNAPSHOT = myDb_snapshot

因此,在每次测试之后,您都可以恢复快照,让您的数据库为下一次测试做好准备。

作为额外注释:在Mocked数据层上运行单元测试不能替代在“真实数据库”上运行测试。从数据库外部看不到许多“连锁”效应。 (触发器/默认值/权限/资源可用性)。 要获得更快的测试用例并生成更多可插拔代码,请模拟您的数据层。但最终您必须在“真实”数据库上运行测试用例。

答案 2 :(得分:3)

不是在每次测试运行之间删除和重新创建数据库,而是如何在事务中运行每个测试以使更改不会持续存在?

将每个测试包裹在System.Transactions.TransactionScope中,当您完成后自动回滚。 (如果您希望回滚,则将TransactionScope分配给变量,然后在其上调用Complete()。)

public void MyTestMethod()
{
    using (new TransactionScope())
    {
        // do your database tests here
        // rolls back when you're done
    }
}

虽然我没有对其进行测试,I've read您可以使用System.EnterpriseServices.TransactionAttribute执行相同操作:

[Transaction(TransactionOption.RequiresNew)]
public void MyTestMethod()
{
    // do your database tests here
    // rolls back when you're done (or so I hear)
}

答案 3 :(得分:1)

单元测试的核心原则之一是你不应该依赖外部事物(文件系统,数据库等)。处理这些问题的一个常见策略是使用模拟对象(我使用EasyMock for java)。如果您很好地构建代码(即构建可靠的数据访问层),您可以模拟负责连接到数据库的对象,并测试依赖于数据访问的代码,而不依赖于数据库。

现在我明白我在这里谈论java /对象,但大多数现代编程语言都提供某种类型的模拟支持。

编辑 - 有时你所能做的就是从你自己的代码开始,考虑到TDD代码,并追溯性地将测试添加到你触摸的所有代码中。最终,你将开始在那1800次测试中取得成功。另外,根据您使用的测试框架,您可以考虑运行两个测试包(NEW和OLD),这样您就可以运行完美的测试状态而无需运行旧测试

答案 4 :(得分:0)

生成数据库不应该太慢和麻烦。你能改变你的配置来运行localhost数据库吗?在每个开发人员的计算机上安装SQL Server并不是很麻烦,测试会更快,因为您将减少网络延迟。

答案 5 :(得分:0)

接受的答案似乎有很小的语法问题。似乎数据库的逻辑名称需要与数据库名称匹配,并且由于我的数据库名称中包含破折号,我还需要将数据库名称包装在方括号中:

CREATE DATABASE [myDb_snapshot] ON
    ( NAME = [myDb], FILENAME = 'C:\MSSQL\Data\myDb_snapshot.ss' )
AS SNAPSHOT OF [myDb];