使用 TSQLT FakeTable 测试由存储过程创建的 table

Using TSQLT FakeTable to test a table created by a Stored Procedure

我正在学习为工作编写单元测试。有人建议我使用 TSQLT FakeTable 来测试由存储过程创建的 table 的某些方面。

在其他单元测试中,我们为存储过程创建一个临时文件table,然后测试临时文件table。我不确定如何将 FakeTable 用于测试。

EXEC tSQLt.NewTestClass 'TestThing'; 
GO                                                 
CREATE OR ALTER PROCEDURE TestThing.[test API_StoredProc to make sure parameters work] 
AS 
BEGIN
    DROP TABLE IF EXISTS #Actual;
    CREATE TABLE #Actual   ----Do I need to create the temp table and the Fake table? I thought I  might need to because I'm testing a table created by a stored procedure.
    ( 
        ISO_3166_Alpha2 NVARCHAR(5),
        ISO_3166_Alpha3 NVARCHAR(5),
        CountryName NVARCHAR(100),
        OfficialStateName NVARCHAR(300),
        sovereigny NVARCHAR(75),
        icon NVARCHAR(100)
    );
    INSERT #Actual
    (
        ISO_3166_Alpha2,
        ISO_3166_Alpha3,
        CountryName,
        OfficialStateName,
        sovereigny,
        icon
    )


    EXEC Marketing.API_StoredProc @Username = 'AnyValue',                              -- varchar(100)
                                                 @FundId = 0,                                 -- int
                                                 @IncludeSalesForceInvestorCountry = NULL,    -- bit
                                                 @IncludeRegisteredSalesJurisdictions = NULL, -- bit
                                                 @IncludeALLCountryForSSRS = NULL,            -- bit
                                                 @WHATIF = NULL,                              -- bit
                                                 @OUTPUT_DEBUG = NULL                         -- bit


   EXEC tsqlt.FakeTable @TableName = N'#Actual',        -- nvarchar(max)  -- How do I differentiate between the faketable and the temp table now? 
                        @SchemaName = N'',       -- nvarchar(max)
                        @Identity = NULL,        -- bit
                        @ComputedColumns = NULL, -- bit
                        @Defaults = NULL         -- bit

  INSERT INTO #Actual 
  (
        ISO_3166_Alpha2,
        ISO_3166_Alpha3,
        CountryName,
        OfficialStateName,
        sovereigny,
        icon
    )
    VALUES 
      ('AF', 'AFG', 'Afghanistan', 'The Islamic Republic of Afghanistan', 'UN MEMBER STATE', 'test')

SELECT * FROM #actual 

END;
GO
EXEC tSQLt.Run 'TestThing';

我试图用上面的代码做的基本上只是为了让 FakeTable 工作。我得到一个错误:"FakeTable couold not resolve the object name #Actual"

我最终要测试的是存储过程中的参数。例如,如果 IncludeSalesForceInvestorCountry 设置为 1,则只应返回某些条目。应返回的内容可能会随着时间而改变,因此这就是我被建议使用 FakeTable 的原因。

在您的方案中,您不需要伪造任何临时 tables,只需伪造 Marketing.API_StoredProc 引用的 table 并用您期望的值填充它会被退回,有些则不会。添加您希望在#expected table 中看到的内容,调用 Marketing.API_StoredProc 将结果转储到 #actual table 中并将结果与​​ tSQLt.AssertEqualsTable.

进行比较

一个好的起点可能是回顾 tSQLT.FakeTable 的工作原理和真实世界的用例。

如您所知,每个单元测试都在其自己的事务中运行,该事务由 tSQLT 框架启动和回滚。当您在单元测试中调用 tSQLt.FakeTable 时,它会暂时重命名指定的 table,然后创建一个完全命名的 table 副本。临时副本允许在每一列中使用 NULL,没有主键或外键、标识列、检查、默认或唯一约束(尽管其中一些可以包含在传真 table 中,具体取决于传递给 [=49= 的参数]).在测试交易期间,任何引用名称 table 的对象都将使用假的而不是真实的 table。在测试结束时,tSQLt 回滚事务,伪造的 table 被删除,原始的 table 返回到它以前的状态(这一切都是自动发生的)。你可能会问,这样做有什么意义?

假设您有一个 [OrderDetail] table,其中包含作为主键的 OrderId 和 ProductId 列、一个 OrderStatusId 列以及一堆其他 NOT NULL 列。这个 table 的 DDL 可能看起来像这样。

CREATE TABLE [dbo].[OrderDetail]
(
  OrderDetailId int IDENTITY(1,1) NOT NULL
, OrderId int NOT NULL
, ProductId int NOT NULL
, OrderStatusId int NOT NULL
, Quantity int NOT NULL
, CostPrice decimal(18,4) NOT NULL
, Discount decimal(6,4) NOT NULL
, DeliveryPreferenceId int NOT NULL
, PromisedDeliveryDate datetime NOT NULL
, DespatchDate datetime NULL
, ActualDeliveryDate datetime NULL
, DeliveryDelayReason varchar(500) NOT NULL
/* ... other NULL and NOT NULL columns */
, CONSTRAINT PK_OrderDetail PRIMARY KEY CLUSTERED (OrderId, ProductId)
, CONSTRAINT AK_OrderDetail_AutoIncrementingId UNIQUE NONCLUSTERED (OrderDetailId)
, CONSTRAINT FK_OrderDetail_Order FOREIGN KEY (OrderId) REFERENCES [dbo].[Orders] (OrderId)
, CONSTRAINT FK_OrderDetail_Product FOREIGN KEY (ProductId) REFERENCES [dbo].[Product] (ProductId)
, CONSTRAINT FK_OrderDetail_OrderStatus FOREIGN KEY (OrderStatusId) REFERENCES [dbo].[OrderStatus] (OrderStatusId)
, CONSTRAINT FK_OrderDetail_DeliveryPreference FOREIGN KEY (DeliveryPreferenceId) REFERENCES [dbo].[DeliveryPreference] (DeliveryPreferenceId)
);

如您所见,此 table 对订单、产品、DeliveryPreference 和 OrderStatus table 具有外键依赖性。 Product 可能反过来有引用 ProductType、BrandCategory、Supplier 等的外键。 Orders table 具有对 Customer、Address 和 SalesPerson 等的外键引用。此链中的所有 table 都有许多定义为 NOT NULL 的列 and/or 受 CHECK 和其他约束的约束。其中一些 table 本身有更多的外键。

现在假设您想编写一个存储过程 (OrderDetailStatusUpdate),其工作是更新 OrderDetail table 上单行的订单状态。它具有三个输入参数@OrderId、@ProductId 和@OrderStatusId。想想你需要做什么来为这个过程设置一个测试。您需要向 OrderDetail table 添加至少两行,包括所有 NOT NULL 列。您还需要将父记录添加到所有 FK 引用的 tables,以及层次结构中位于其上方的任何 tables,确保您的所有插入符合所有可空性和其他约束在那些 table 上也是如此。据我统计,至少有 11 table 需要填充,全部用于一个简单的测试。即使你硬着头皮做了所有这些设置,在未来的某个时候,有人可能(可能会)出现并向其中一个 table 添加一个新的 NOT NULL 列或更改约束这将导致您的测试失败 - 而该失败实际上与您的测试或您正在测试的存储过程无关。测试驱动开发的基本原则之一是测试应该只有失败的原因,我数了几十个。

tSQLT.FakeTable 救援。

为该程序设置测试,您实际至少需要做什么?您需要两行到 OrderDetail table(一个更新,一个不更新),您实际“需要”考虑的唯一列是 OrderId 和 ProductId(标识键)加上 OrderStatusId - 正在更新的列更新。其余列虽然在整体设计中很重要,但与被测对象无关。在您对 OrderDetailStatusUpdate 的测试中,您将遵循以下步骤:

  • 呼叫tSQLt.FakeTable‘dbo.OrderDetail’
  • 创建一个#expected table(带有 OrderId、ProductId 和 OrderStatusId 列)并用您希望最终得到的两行填充它 (一个将具有预期的 OrderStatusId,另一个可以为 NULL)
  • 向现在模拟的 OrderDetail 添加两行 table(OrderId 和 仅限 ProductId)
  • 调用被测程序OrderDetailStatusUpdate传递 插入行之一的 OrderID 和 ProductID 加上 您要更改为的 OrderStatusId。
  • 使用 tSQLt.AssertEqualsTable 将#expected table 与 订单详情 table。这个断言只会比较列 #expected table,OrderDetail 的其他列将被忽略

创建此测试非常快,它可能失败的唯一原因是底层模式中与被测代码相关的某些内容发生了变化。更改 OrderDetail table 或任何 parent/grand-parent table 中的任何其他列都不会导致此测试中断。

所以使用 tSQLt.FakeTable(或任何其他类型的模拟对象)的原因是为了提供真正强大的测试隔离和简单的测试数据准备。