在代码库中的某处,有一行代码执行连接到远程数据库的实际操作。该代码行(十分之九)是对特定于您的语言和环境的运行时库提供的“内置”方法的调用。因此,它不是“您的”代码,因此您不需要对其进行测试;为了进行单元测试,您可以相信此方法调用将正确执行。您可以并且应该仍然在单元测试套件中进行测试的是诸如确保用于此调用的参数与您期望的参数一样的事情,例如确保连接字符串正确,SQL语句或存储过程名称。
这是单元测试不应离开运行时“沙箱”并依赖于外部状态的限制的目的之一。实际上很实用;单元测试的目的是验证您编写的(或将要在TDD中编写的)代码是否按预期的方式运行。您未编写的代码(例如用于执行数据库操作的库)不应作为任何单元测试范围的一部分,这是因为您没有编写代码非常简单。
在您的集成测试套件中,这些限制得到了放松。现在你可以设计接触数据库的测试,以确保您编写的代码与未编写的代码能很好地配合使用。但是,这两个测试套件应该保持隔离,因为您的单元测试套件运行得更快,因此效率更高(因此您可以快速验证开发人员对其代码的所有断言仍然成立),并且按照定义,集成测试由于增加了对外部资源的依赖性,因此速度降低了几个数量级。让构建机器人每隔几个小时运行一次完整的集成套件,执行测试以锁定外部资源,这样开发人员就不会通过在本地运行这些相同的测试来踩对方的脚。如果构建中断,那又如何呢?确保构建机器人永远不会使构建失败的重要性远远超过应有的重要性。
现在,您能否严格遵守此规则取决于您连接和查询数据库的确切策略。在许多情况下,您必须使用“裸露的”数据访问框架,例如ADO.NET的SqlConnection和SqlStatement对象,由您开发的整个方法可能由内置方法调用和其他依赖于具有数据库连接,因此在这种情况下,您最好的办法是模拟整个功能并信任您的集成测试套件。它还取决于您设计类的意愿,以允许出于测试目的而替换特定的代码行(例如Tobi对Template Method模式的建议,这是一个很好的选择,因为它允许“部分模拟”
如果您的数据持久性模型依赖于数据层中的代码(例如触发器,存储的proc等),那么除了开发驻留在数据层内部或跨数据层的测试之外,别无选择地行使您自己正在编写的代码应用程序运行时与DBMS之间的边界。出于这个原因,纯粹主义者会说要避免这种模式,而应该使用ORM之类的东西。我认为我不会走那么远。即使在语言集成查询和其他经过编译器检查,依赖于域的持久性操作的时代,我也看到了将数据库锁定为仅通过存储过程公开的操作的价值,当然,必须使用自动方法来验证此类存储过程测试。但是,此类测试不是单元测试。他们是整合 测试。
如果您对此区分有疑问,通常是基于对完整的“代码覆盖率”或“单元测试覆盖率”的高度重视。您想确保单元测试覆盖代码的每一行。脸上有一个崇高的目标,但我要说“洗碗”。这种心态适合于反模式拉伸远远超出了这个特定的情况下,如执行,但不写assertionless测试演习您的代码。仅出于覆盖范围的目的,这些类型的最终运行比放宽最低覆盖范围更有害。如果您要确保代码库的每一行都通过某种自动化测试执行,那么这很容易;在计算代码覆盖率指标时,请包括集成测试。您甚至可以更进一步,隔离这些有争议的“ Itino”测试(“仅名称中的集成”),并且在您的单元测试套件和集成测试的这一子类别(仍应运行得相当快)之间,您应该会感到头疼几乎接近全覆盖。