如何测试使用tSQLt更新表(相对于返回结果集)的TSQL存储过程

如何测试使用tSQLt更新表(相对于返回结果集)的TSQL存储过程,tsql,testing,stored-procedures,tsqlt,Tsql,Testing,Stored Procedures,Tsqlt,tSQLt专家能否插话介绍一下测试一种存储过程的方法,这种存储过程不返回任何内容,而是在表中执行字段更新?我知道如何测试从函数或SP返回的结果,但是如果是就地更新,如果过程的表名是硬编码的,我如何针对假表和实际表运行测试?我能想到的唯一方法是让整个SP使用动态SQL并将表名作为参数传递,但这会降低代码的可读性,使代码更加脆弱。有更好的办法吗?下面是一个简单的存储过程,它查看其他两个字段:TransactionDate和ENdOfDropDate,并根据条件的结果将同一表中名为“IsWithInA

tSQLt专家能否插话介绍一下测试一种存储过程的方法,这种存储过程不返回任何内容,而是在表中执行字段更新?我知道如何测试从函数或SP返回的结果,但是如果是就地更新,如果过程的表名是硬编码的,我如何针对假表和实际表运行测试?我能想到的唯一方法是让整个SP使用动态SQL并将表名作为参数传递,但这会降低代码的可读性,使代码更加脆弱。有更好的办法吗?下面是一个简单的存储过程,它查看其他两个字段:TransactionDate和ENdOfDropDate,并根据条件的结果将同一表中名为“IsWithInAddress”的第三个字段设置为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

谢谢

解决方案是首先模拟表以隔离任何依赖项(外键等)。然后添加足够的数据来测试您想要涵盖的所有情况(请参见下面示例中的注释),并在运行测试过程后使用
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]';


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

通常,测试此类程序的最佳方法是制作目标表(t1)的较小副本,并将该过程设置为在副本上执行。我还看到这种情况在更大范围内发生,为了测试单个批处理作业,将创建和填充整个数据库。谢谢,@datacentricity。在昨天有人给我指出这篇文章之后,我找到了一个非常类似的解决方案:对于我来说,表替换是如何在测试中发生的,以及为什么该过程从未更新“真实”表,但它是有效的,这仍然是一个谜!因此,当您调用tSQLt.FakeTable时,实际表将被重新命名,并使用实际名称创建表的副本。sproc没有意识到这一点,非常高兴地与假表而不是真表竞争。这一切都发生在一个事务的范围内,该事务在测试结束时自动回滚(不管结果如何)。可伪造的真正聪明的事情是,模拟表没有约束(PK,UNQ,FK,CK等),所有列都允许NULL,这意味着每个测试只需要考虑与Teste相关的表和列。想象一个OrderDetail表,它有许多非空列以及FK到OrderHeader、Product等。Product表可能有FK到Supplier、ProductType等,OrderHeader可能有FK到Customer、SalesPerson等。现在想象一个名为OrderDetailUpdateStatus的存储过程,它有如下简单的逻辑“更新OrderDetail集StatusId=@NewStatusId,其中OrderDetailId=@OrderDetail”“。若要为此存储过程编写测试,您只对StatusId和OrderDetailId列感兴趣-您不关心任何其他列或所有依赖表。感谢您的解释。”。您刚才提到了一个重要的问题—如果实际表被重命名,是否意味着其他进程在测试执行期间找不到它?也就是说,如果我有一个在“真实”表上运行的计划作业,并且我运行20-30个测试,每个测试需要几秒钟,那么可以想象,该表将在一分钟内消失?视情况而定的视图如何?他们也会崩溃吗?我不打算在prod中运行测试框架,但这使得即使在开发环境中运行也有潜在的“危险”,或者我遗漏了什么?测试事务之外的任何通常访问实际表的内容都将被打开的测试事务阻止。这使我想到一个非常重要的问题。您不应该在生产环境中使用tSQLt。tSQLt是一个单元测试框架,最好由开发人员在专用沙箱中使用,或者作为CI过程的一部分在专用构建环境中使用。您可以在共享开发环境中使用它,但如果多个开发人员同时运行测试套件,则仍然可能会遇到问题。我在一个沙箱中做所有事情,甚至不将tSQLt或单元测试部署到共享开发人员。

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]';