【问题标题】:How to test a TSQL stored procedure that updates a table (vs. returning a result set) using tSQLt如何使用 tSQLt 测试更新表(相对于返回结果集)的 SQL 存储过程
【发布时间】:2016-03-15 15:44:10
【问题描述】:

能否请 tSQLt 专家加入测试一种不返回任何内容但在表中执行字段更新的存储过程的方法?我了解如何测试函数或 SP 返回的结果,但在就地更新的情况下,如果该过程,我如何使测试针对假表与实际表运行表名有硬编码吗?我能想到的唯一办法就是让整个SP使用动态SQL,并把表名作为参数传进去,但是这样会让代码的可读性变差,更脆。有没有更好的办法?下面是一个简单的存储过程,它查看另外两个字段:TransactionDate 和 ENdOfDropDate,并根据条件的结果将同一个表中名为“IsWithinAddDrop”的第三个字段设置为 True 或 False。

create table  tblT1
(
   ID [bigint] IDENTITY(1,1) NOT NULL,
   TransactionDate   [datetime2](7) NULL,
   EndOfDropDate      [datetime2](7) NULL, 
   IsWithinAddDrop [nvarchar](10) NULL
)

insert into tblT1 (TransactionDate, EndOfDropDate) values ('1/1/2016',  '2/1/2016')
insert into tblT1 (TransactionDate, EndOfDropDate) values ('2/1/2016',  '1/2/2016')
insert into tblT1 (TransactionDate, EndOfDropDate) values ('3/1/2016',  '3/1/2016')

create procedure spUpdateIsWithinAddDrop
as 
begin
    Update t1
        set t1.IsWithinAddDrop =
        (case
            when t1.TransactionDate <= t1.EndOfDropDate then 'True'
            else 'False'
        end)
        from tblT1 t1
end

exec spUpdateIsWithinAddDrop

结果是我要测试的表中更新的 IsWithinAddDrop 列:

TransactionDate EndOfDropDate   IsWithinAddDrop
2016-01-01      2016-02-01      True
2016-02-01      2016-01-02      False
2016-03-01      2016-03-01      True

谢谢!

【问题讨论】:

  • 通常测试此类过程的最佳方法是制作目标表 (t1) 的较小副本,并将过程设置为在副本上执行。我还看到这种情况发生在更大的规模上,其中将创建和填充整个数据库以测试单个批处理作业。

标签: tsql testing stored-procedures tsqlt


【解决方案1】:

解决方案是首先模拟表以隔离任何依赖项(外键等)。然后添加足够的数据来测试您想要涵盖的所有情况(请参阅下面示例中的 cmets),并在运行被测过程后使用 tSQLt.AssertEqualsTable 将目标表的内容与一组预定义的预期行进行比较。

if schema_id(N'StackModuleTests') is null exec tSQLt.NewTestClass @ClassName = N'StackModuleTests' go if objectpropertyex(object_id(N'[StackModuleTests].[test spUpdateIsWithinAddDrop example]'), N'IsProcedure') = 1 drop procedure [StackModuleTests].[test spUpdateIsWithinAddDrop example] go create procedure [StackModuleTests].[test spUpdateIsWithinAddDrop example] as begin --! Start by faking the table that will be updated to isolate this test from any other dependencies exec tSQLt.FakeTable @TableName = 'dbo.tblT1' ; --! We expect spUpdateIsWithinAddDrop to set IsWithinAddDrop to TRUE only if --! TransactionDate is less than or equal to EndOfDropDate so we need the --! following tests: --! --! Positive case where TransactionDate equals EndOfDropDate --! Positive case where TransactionDate less than EndOfDropDate --! Negative case where TransactionDate more than EndOfDropDate --! May want other tests to cover scenarios where either column is null --! Purists would say that this should one unit test for each case, personally --! I feel that as SQL is a set based language it is OK to combine all cases --! into a single test (also minimises all the setup) --! --! Assemble the data required for all test cases insert into tblT1 (TransactionDate, EndOfDropDate) values ('20160101', '20160101') , ('20160101', '20160102') , ('20160102', '20160101') ; --! What do we expect to see afterwards? create table #expected ( TransactionDate [datetime2](7) null , EndOfDropDate [datetime2](7) null , IsWithinAddDrop [nvarchar](10) null ) insert into #expected (TransactionDate, EndOfDropDate, IsWithinAddDrop) values ('20160101', '20160101', 'True') , ('20160101', '20160102', 'True') , ('20160102', '20160101', 'False') ; --! Act exec dbo.spUpdateIsWithinAddDrop ; --! Assert that the contents of tblT1 now match the #expected contents --! Notice that we ignore the ID column completely in this test because --! it has nothing to do with the object under test (spUpdateIsWithinAddDrop) exec tSQLt.AssertEqualsTable @Expected = N'#expected', @Actual = N'tblT1' ; end go exec tSQLt.Run '[StackModuleTests].[test spUpdateIsWithinAddDrop example]';

希望这充分解释了该方法,但如果没有,请寻求进一步澄清。

【讨论】:

  • 谢谢@datacentricity。昨天有人向我指出这篇文章后,我得出了一个非常相似的解决方案:theproactiveprogrammer.com/design/sql-unit-testing 在测试中表替换是如何发生的以及为什么该过程从未更新“真实”表,但它有效!
  • 因此,当您调用 tSQLt.FakeTable 时会发生什么情况是真实表被重新命名并使用真实名称创建表的副本。 sproc 没有意识到这一点,并且非常高兴地运行在假表而不是真实表上。这一切都发生在事务的范围内,该事务在测试结束时自动回滚(无论结果如何)。 FakeTable 的真正聪明之处在于,模拟表没有约束(PK、UNQ、FK、CK 等),所有列都允许为 NULL,这意味着每个测试只需要考虑与测试相关的表和列跨度>
  • 例如想象一个 OrderDetail 表,它有许多 NOT NULL 列以及对 OrderHeader、Product 等的 FK。Product 表可能对 Supplier、ProductType 等有 FK,而 OrderHeader 可能对 Customer、SalesPerson 等有 FK。现在想象一个名为 OrderDetailUpdateStatus 的存储过程,它具有简单的逻辑,例如“UPDATE OrderDetail SET StatusId = @NewStatusId WHERE OrderDetailId = @OrderDetail”。要为此存储过程编写测试,您只对 StatusId 和 OrderDetailId 列感兴趣 - 您不关心任何其他列或所有依赖表。
  • 感谢您的解释。您刚刚提出了一个重点——如果真正的表被重命名,这是否意味着其他进程在测试执行期间找不到它? IE。如果我在那个“真实”表上运行了一个预定的工作,并且我运行了 20-30 个测试,每个测试需要几秒钟,那么可以想象这个表会有效地消失一分钟吗?取决于它的视图呢?他们也会崩溃吗?我不打算在 prod 中运行测试框架,但这使得即使在开发环境中运行也可能“危险”,或者我错过了什么?
  • 正常访问真实表的测试事务之外的任何东西都将被打开的测试事务阻塞。这让我想到了一个非常重要的观点。你不应该(即从不)在生产环境中使用 tSQLt。 tSQLt 是一个单元测试框架,最适合开发人员在专用沙箱或专用构建环境中用作 CI 过程的一部分。您可以在共享的开发环境中使用它,但如果多个开发人员同时运行测试套件,仍可能会遇到问题。我在沙盒中做所有事情,甚至不将 tSQLt 或单元测试部署到共享 DEV。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-31
  • 1970-01-01
  • 1970-01-01
  • 2014-12-07
  • 2023-04-06
相关资源
最近更新 更多