测试存储过程的最佳方法是什么?
就像许多要求通过存储过程进行所有访问的公司一样,我们似乎将许多业务逻辑锁定在存储过程中。这些东西简直很难测试,其中一些已经变得很愚蠢了。有没有人有一套最佳实践,可以使放心地测试这些事情变得容易一些?
目前,我们维护着30个左右的"问题"数据库。这并不总是特别有据可查,并且肯定不是自动化的。
解决方案
一位同事发誓TSQLUnit测试框架。可能值得一看需求。
不知道这是否是我们要查找的内容,但是由于我们使用的是SQL Server:我发现LINQ是测试存储过程的绝佳工具。我们只需将存储过程拖到DBML图上,然后在数据上下文中将它们作为方法调用即可。可以为测试工具设置ADO连接等。例如,如果我们在Visual Studio中设置测试项目,则可以简单地测试过程,例如对另一个对象的方法。如果我们存储的proc返回结果集,我认为LINQ会将其转换为匿名变量,我们应该可以通过IEnumerable或者IQueryable进行访问(有人请对此进行验证)。但是,如果我们仅返回返回码,则这应该是一种快速且相当简单的方法。
我注意到帖子被标记为SqlServer。如果是这种情况,那么我们应该查看Visual Studio一部分的数据库专业版团队版。这里有一些文章:
- 我写的关于使用DBPro TDD存储过程的教程
- MSDN杂志文章,其中有更深入的介绍
- DbFit,与FIT和Fitnesse集成以进行数据库功能测试的框架
最后一个实际上是跨数据库平台,而DBPro目前仅是SQL Server。
这似乎是一个可怕的政策。也许我们可以编写一个执行SQL的存储过程,然后开始转换代码以在其中运行。
无论如何,我都会通过传统的自动化框架测试调用存储过程。作为应用程序和数据之间的网关,应将它们作为集成测试而不是纯单元测试来处理。但是,我们可以使用基于xUnit的单元测试框架来驱动它们。只要测试可以访问针对数据库运行SQL的权限,也许可以通过前面提到的方法,我们就可以断言已进行了正确的更改。
挑战之一是我们表明它们越来越长。我建议将它们分成子例程,并使其尽可能小。它使测试更容易,维护也更容易。
我们有一个非常薄的数据访问层,该层基本上将存储过程构造为类似于Cmethods的外观。然后,我们的NUnit测试套件具有SetUp / TearDown来创建/回滚事务,并测试调用DAL的方法。没什么,而且证明比TSQLUnit测试套件更易于维护。
这是我的低技术,快速方法,仅将示例输入保留在DDL中
USE [SpacelySprockets] GO /****** Object: StoredProcedure [dbo].[uspBrownNoseMrSpacely] Script Date: 02/03/3000 00:24:41 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO --================================ --Stored Procedure DDL: --================================ --Example Inputs /* DECLARE @SuckupPloyId int DECLARE @SuckupIdentityRecordId int SET @SuckupPloyId = 3 */ -- ============================================= -- Author: 6eorge Jetson -- Create date: 01/02/3000 -- Description: Sucks up to the boss -- ============================================= CREATE PROCEDURE [dbo].[uspBrownNoseMrSpacely] @SuckupPloyId int ,@SuckupIdentityRecordId int OUTPUT AS BEGIN DECLARE @EmployeeId int DECLARE @SuckupPoints int DECLARE @DateTimeStamp datetime SET @EmployeeId = dbo.svfGetEmployeeId('6eorge Jetson') SET @SuckupPoints = dbo.svfGetSuckupPoints(@SuckupPloyId) SET @DateTimeStamp = getdate() --Data state-changing statement in sproc INSERT INTO [dbo].[tblSuckupPointsEarned]([EmployeeId], [SuckupPoints], [DateTimeStamp] ) VALUES (@EmployeeId, @SuckupPoints, @DateTimeStamp) SET @SuckupIdentityRecordId = @@Identity END --Unit Test Evidence Display /* SELECT @EmployeeId as EmployeeId ,@SuckupPoints as SuckupPoints ,@DateTimeStamp as DateTimeStamp */ --========================================================================== --After editing for low-tech, non-state changing "unit-like" test invocation --========================================================================== --Example Inputs DECLARE @SuckupPloyId int DECLARE @SuckupIdentityRecordId int SET @SuckupPloyId = 3 /* -- ============================================= -- Author: 6eorge Jetson -- Create date: 01/02/3000 -- Description: Sucks up to the boss -- ============================================= CREATE PROCEDURE [dbo].[uspBrownNoseMrSpacely] @SuckupPloyId int ,@SuckupIdentityRecordId int OUTPUT AS BEGIN */ DECLARE @EmployeeId int DECLARE @SuckupPoints int DECLARE @DateTimeStamp datetime SET @EmployeeId = dbo.svfGetEmployeeId('6eorge Jetson') SET @SuckupPoints = dbo.svfGetSuckupPoints(@SuckupPloyId) SET @DateTimeStamp = getdate() --Data state-changing statement now commented out to prevent data state change -- INSERT INTO [dbo].[tblSuckupPointsEarned]([EmployeeId], [SuckupPoints], [DateTimeStamp] ) -- VALUES (@EmployeeId, @SuckupPoints, @DateTimeStamp) SET @SuckupIdentityRecordId = @@Identity --END --Need to comment out the sproc "END" also --Unit Test Evidence Display SELECT @EmployeeId as EmployeeId ,@SuckupPoints as SuckupPoints ,@DateTimeStamp as DateTimeStamp
它对于udfs甚至更好,因为无需担心状态变化。
显然,我不建议我们使用它代替测试框架,
但是如果我坚持这种简单的以秒为单位的成本计算准则,
Assert that my managable-sized sproc passes at least a simple "unit test"
在执行CREATE PROCEDURE之前,我发现我犯了更少的错误(可能是因为纪律比测试本身更多)。
我使用的一种方法是编写用于重构特定存储过程的"临时"单元测试。我们可以从数据库中的一组查询中保存数据,并将其存储在可以进行单元测试的位置。
然后,重构proc库存。返回的数据应该相同,并且可以自动或者手动直接与保存的数据进行比较。
一种替代方法是并行运行两个存储过程,并比较结果集。
这对于仅选择存储的过程特别有效,但是更新,插入和删除更为复杂。
我已经使用这种方法来使代码达到更容易进行单元测试,或者更简单或者两者兼而有之的状态。