演练:创建和运行 SQL Server 单元测试

在本演练中,将创建一个 SQL Server 单元测试,该测试验证多个存储过程的行为。 创建 SQL Server 单元测试可帮助确定可能会引发不正确的应用程序行为的代码缺陷。 可以将 SQL Server 单元测试和应用程序测试作为一组自动执行的测试的一部分来运行。

在本演练中,您将执行下列任务:

在某个单元测试检测到存储过程中的错误后,请更正该错误并重新运行测试。

先决条件

若要完成本演练,您必须能够连接到有权在其上创建和部署数据库的数据库服务器(或 LocalDB 数据库)。 有关详细信息,请参阅 执行 Visual Studio 的数据库功能所需的权限

创建包含数据库架构的脚本

  1. 在“ 文件 ”菜单上,指向“ 新建”,然后选择“ 文件”。

    此时将显示 “新建文件” 对话框。

  2. “类别 ”列表中,选择“ 常规 ”(如果尚未突出显示)。

  3. “模板” 列表中,选择 “Sql 文件”,然后选择“ 打开”。

    将打开 Transact-SQL 编辑器。

  4. 复制以下 Transact-SQL 代码,并将其粘贴到 Transact-SQL 编辑器中。

    PRINT N'Creating Sales...';
    GO
    
    CREATE SCHEMA [Sales]
        AUTHORIZATION [dbo];
    GO
    
    PRINT N'Creating Sales.Customer...';
    GO
    
    CREATE TABLE [Sales].[Customer]
    (
        [CustomerID] INT IDENTITY (1, 1) NOT NULL,
        [CustomerName] NVARCHAR (40) NOT NULL,
        [YTDOrders] INT NOT NULL,
        [YTDSales] INT NOT NULL
    );
    GO
    
    PRINT N'Creating Sales.Orders...';
    GO
    
    CREATE TABLE [Sales].[Orders]
    (
        [CustomerID] INT NOT NULL,
        [OrderID] INT IDENTITY (1, 1) NOT NULL,
        [OrderDate] DATETIME NOT NULL,
        [FilledDate] DATETIME NULL,
        [Status] CHAR (1) NOT NULL,
        [Amount] INT NOT NULL
    );
    GO
    
    PRINT N'Creating Sales.Def_Customer_YTDOrders...';
    GO
    
    ALTER TABLE [Sales].[Customer]
        ADD CONSTRAINT [Def_Customer_YTDOrders] DEFAULT 0 FOR [YTDOrders];
    GO
    
    PRINT N'Creating Sales.Def_Customer_YTDSales...';
    GO
    
    ALTER TABLE [Sales].[Customer]
        ADD CONSTRAINT [Def_Customer_YTDSales] DEFAULT 0 FOR [YTDSales];
    GO
    
    PRINT N'Creating Sales.Def_Orders_OrderDate...';
    GO
    
    ALTER TABLE [Sales].[Orders]
        ADD CONSTRAINT [Def_Orders_OrderDate] DEFAULT GetDate() FOR [OrderDate];
    GO
    
    PRINT N'Creating Sales.Def_Orders_Status...';
    GO
    
    ALTER TABLE [Sales].[Orders]
        ADD CONSTRAINT [Def_Orders_Status] DEFAULT 'O' FOR [Status];
    GO
    
    PRINT N'Creating Sales.PK_Customer_CustID...';
    GO
    
    ALTER TABLE [Sales].[Customer]
        ADD CONSTRAINT [PK_Customer_CustID] PRIMARY KEY CLUSTERED ([CustomerID] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF);
    GO
    
    PRINT N'Creating Sales.PK_Orders_OrderID...';
    GO
    
    ALTER TABLE [Sales].[Orders]
        ADD CONSTRAINT [PK_Orders_OrderID] PRIMARY KEY CLUSTERED ([OrderID] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF);
    GO
    
    PRINT N'Creating Sales.FK_Orders_Customer_CustID...';
    GO
    
    ALTER TABLE [Sales].[Orders]
        ADD CONSTRAINT [FK_Orders_Customer_CustID] FOREIGN KEY ([CustomerID]) REFERENCES [Sales].[Customer] ([CustomerID]) ON DELETE NO ACTION ON UPDATE NO ACTION;
    GO
    
    PRINT N'Creating Sales.CK_Orders_FilledDate...';
    GO
    
    ALTER TABLE [Sales].[Orders]
        ADD CONSTRAINT [CK_Orders_FilledDate] CHECK ((FilledDate >= OrderDate)
                                                     AND (FilledDate < '01/01/2030'));
    GO
    
    PRINT N'Creating Sales.CK_Orders_OrderDate...';
    GO
    
    ALTER TABLE [Sales].[Orders]
        ADD CONSTRAINT [CK_Orders_OrderDate] CHECK ((OrderDate > '01/01/2005')
                                                    AND (OrderDate < '01/01/2030'));
    GO
    
    PRINT N'Creating Sales.uspCancelOrder...';
    GO
    
    CREATE PROCEDURE [Sales].[uspCancelOrder]
    @OrderID INT
    AS
    BEGIN
        DECLARE @Delta AS INT, @CustomerID AS INT;
        BEGIN TRANSACTION;
        SELECT @Delta = [Amount],
               @CustomerID = [CustomerID]
        FROM [Sales].[Orders]
        WHERE [OrderID] = @OrderID;
        UPDATE [Sales].[Orders]
            SET [Status] = 'X'
        WHERE [OrderID] = @OrderID;
        UPDATE [Sales].[Customer]
            SET YTDOrders = YTDOrders - @Delta
        WHERE [CustomerID] = @CustomerID;
        COMMIT TRANSACTION;
    END
    GO
    
    PRINT N'Creating Sales.uspFillOrder...';
    GO
    
    CREATE PROCEDURE [Sales].[uspFillOrder]
    @OrderID INT, @FilledDate DATETIME
    AS
    BEGIN
        DECLARE @Delta AS INT, @CustomerID AS INT;
        BEGIN TRANSACTION;
        SELECT @Delta = [Amount],
               @CustomerID = [CustomerID]
        FROM [Sales].[Orders]
        WHERE [OrderID] = @OrderID;
        UPDATE [Sales].[Orders]
            SET [Status]     = 'F',
                [FilledDate] = @FilledDate
        WHERE [OrderID] = @OrderID;
        UPDATE [Sales].[Customer]
            SET YTDSales = YTDSales - @Delta
        WHERE [CustomerID] = @CustomerID;
        COMMIT TRANSACTION;
    END
    GO
    
    PRINT N'Creating Sales.uspNewCustomer...';
    GO
    
    CREATE PROCEDURE [Sales].[uspNewCustomer]
    @CustomerName NVARCHAR (40)
    AS
    BEGIN
        INSERT INTO [Sales].[Customer] (CustomerName)
        VALUES (@CustomerName);
        RETURN SCOPE_IDENTITY();
    END
    GO
    
    PRINT N'Creating Sales.uspPlaceNewOrder...';
    GO
    
    CREATE PROCEDURE [Sales].[uspPlaceNewOrder]
    @CustomerID INT, @Amount INT, @OrderDate DATETIME, @Status CHAR (1)='O'
    AS
    BEGIN
        DECLARE @RC AS INT;
        BEGIN TRANSACTION;
        INSERT INTO [Sales].[Orders] (CustomerID, OrderDate, FilledDate, Status, Amount)
        VALUES (@CustomerID, @OrderDate, NULL, @Status, @Amount);
        SELECT @RC = SCOPE_IDENTITY();
        UPDATE [Sales].[Customer]
            SET YTDOrders = YTDOrders + @Amount
        WHERE [CustomerID] = @CustomerID;
        COMMIT TRANSACTION;
        RETURN @RC;
    END
    GO
    
    CREATE PROCEDURE [Sales].[uspShowOrderDetails]
    @CustomerID INT=0
    AS
    BEGIN
        SELECT [C].[CustomerName],
               CONVERT (DATE, [O].[OrderDate]),
               CONVERT (DATE, [O].[FilledDate]),
               [O].[Status],
               [O].[Amount]
        FROM [Sales].[Customer] AS C
             INNER JOIN [Sales].[Orders] AS O
                 ON [O].[CustomerID] = [C].[CustomerID]
        WHERE [C].[CustomerID] = @CustomerID;
    END
    GO
    
  5. 保存文件。 记下该位置,因为在下一个过程中必须使用此脚本。

  6. “文件 ”菜单上,选择“ 关闭解决方案”。

    接下来,创建一个数据库项目并从已创建的脚本导入架构。

创建数据库项目并导入架构

创建一个数据库项目

  1. “文件 ”菜单上,指向“ 新建”,然后选择“ 项目”。

    将显示“新建项目”对话框。

  2. 在“已安装的模板”下,选择“SQL Server”节点,然后选择“SQL Server 数据库项目”。

  3. Name 中,键入 SimpleUnitTestDB

  4. 如果尚未选中 为解决方案创建目录 的复选框,请选中该复选框。

  5. 如果尚未清除,请清除“ 添加到源代码管理 ”复选框,然后选择“ 确定”。

    将创建数据库项目,并且它将显示在 “解决方案资源管理器” 中。 接下来,从脚本导入数据库架构。

从脚本导入数据库架构

  1. “项目”菜单上,选择“导入”,然后选择“脚本”(*.sql)。

  2. 阅读欢迎页后,选择 “下一步 ”。

  3. 选择 “浏览”,然后转到保存 .sql 文件的目录。

  4. 双击 .sql 该文件,然后选择“ 完成”。

    将导入脚本,并且该脚本中定义的对象将添加到您的数据库项目中。

  5. 查看摘要,然后选择“ 完成 ”以完成作。

    注意

    Sales.uspFillOrder 过程包含你稍后在此过程中发现和更正的有意编码错误。

检查生成的项目

  1. “解决方案资源管理器” 中,检查已导入到项目中的脚本文件。

  2. 在“SQL Server 对象资源管理器”中,在“项目”节点中查看该数据库。

部署到 LocalDB

默认情况下,按 F5 时,将数据库部署到 LocalDB 数据库(或发布)。 您可以通过转到项目属性页的“调试”选项卡并更改连接字符串,更改数据库位置。

创建 SQL Server 单元测试

为存储过程创建 SQL Server 单元测试

  1. SQL Server 对象资源管理器中,展开SimpleUnitTestDB项目节点,然后展开可编程性,接着展开存储过程节点。

  2. 右键单击其中一个存储过程,然后选择“ 创建单元测试 ”以显示“ 创建单元测试 ”对话框。

  3. 选中所有五个存储过程对应的复选框:“Sales.uspCancelOrder” 、“Sales.uspFillOrder” 、“Sales.uspNewCustomer” 、“Sales.uspPlaceNewOrder” 和“Sales.uspShowOrderDetails”

  4. “项目 ”下拉列表中,选择“ 创建新的 C# 测试项目”。

  5. 接受项目名称和类名的默认名称,然后选择“ 确定”。

  6. 在测试配置对话框的 “使用以下数据连接执行单元测试”中,指定与您在本演练前面部署的数据库的连接。 例如,如果使用默认部署位置(即 LocalDB),则选择“ 新建连接 指定 ”(LocalDB)\Projects。 然后,选择该数据库的名称。 然后,选择“ 确定 ”关闭 “连接属性 ”对话框。

    注意

    如果必须测试具有有限权限的视图或存储过程,则通常会在此步骤中指定该连接。 然后,指定具有更广泛权限的辅助连接来验证测试。 如果你有辅助连接,则应将该用户添加到数据库项目,并在预部署脚本中为该用户创建登录名。

  7. 在测试配置对话框的 “部署” 部分中,选中 “运行单元测试前,自动部署该数据库项目” 复选框。

  8. 数据库项目中,选择 SimpleUnitTestDB.sqlproj

  9. 部署配置中,选择“ 调试”。

    在 SQL Server 单元测试过程中,可能还需要生成测试数据。 在本演练中,将跳过此步骤,因为测试会创建自己的数据。

  10. 选择“确定”

    将生成测试项目,并且将显示 SQL Server 单元测试设计器。 接下来,更新单元测试 Transact-SQL 脚本中的测试逻辑。

定义测试逻辑

此基本数据库有两个表, Customer 以及 Order。 可以使用以下存储过程更新数据库:

存储过程 Description
uspNewCustomer 此存储过程将记录添加到 Customer 表中,该记录将客户的 YTDOrdersYTDSales 设置为零。
uspPlaceNewOrder 此存储过程将记录添加到 Orders 指定客户的表中,并更新 YTDOrders 表中相应记录上的 Customer 值。
uspFillOrder 此存储过程通过将Orders表中记录的状态从“O”更改为“F”来更新记录,并增加YTDSales表中相应记录的Customer量。
uspCancelOrder 此存储过程通过将状态从“O”更改为“X”来更新表中的记录Orders,并将表中相应记录上的YTDOrders金额递减Customer
uspShowOrderDetails 此存储过程将 Orders 表与 Custom 表联接,并显示特定客户的相关记录。

注意

此示例演示如何创建基本的 SQL Server 单元测试。 在实际数据库中,您可以对特定客户的状态为“O”或“F”的所有订单的总额进行合计。 本演练中的过程也没有包含错误处理。 例如,这些机制不会阻止你调用 uspFillOrder 已完成的订单。

测试假定数据库以干净状态启动。 创建验证以下条件的测试:

  • uspNewCustomer:运行存储过程后, Customer 验证该表是否包含一行。

  • uspPlaceNewOrder:对于拥有 CustomerID 为 1 的客户,下订单金额为 100 美元。 验证YTDOrders该客户的金额为 100,且YTDSales的金额为零。

  • uspFillOrder:对于有 CustomerID 为 1 的客户,下订单,金额为 50 美元。 填充该订单。 验证YTDOrdersYTDSales的金额是否均为50。

  • uspShowOrderDetails:对于拥有 CustomerID 1 的客户,请分别订购 100 美元、50 美元和 5 美元。 验证是否 uspShowOrderDetails 返回正确的列数,结果集是否具有预期的校验和。

注意

为获得一组完整的 SQL Server 单元测试,通常需要验证其他列的设置是否正确。 为了保持本演练内容的简洁,未描述如何验证uspCancelOrder的行为。

为 uspNewCustomer 编写 SQL Server 单元测试

  1. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspNewCustomerTest,并确保 测试在相邻 列表中突出显示。

    在执行上一步后,可以为单元测试中的测试操作创建测试脚本。

  2. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    -- ssNoVersion unit test for Sales.uspNewCustomer
    DECLARE @RC AS INT, @CustomerName AS NVARCHAR (40);
    
    SELECT @RC = 0,
           @CustomerName = 'Fictitious Customer';
    
    EXECUTE
        @RC = [Sales].[uspNewCustomer]
        @CustomerName;
    
    SELECT *
    FROM [Sales].[Customer];
    
  3. “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件 ”图标(红色 X)。

  4. “测试条件 ”窗格中,选择列表中的 行计数 ,然后选择 “添加测试条件 ”图标(绿色 +)。

  5. 打开 “属性” 窗口(选择测试条件并按 F4),并将 “行计数 ”属性设置为 1。

  6. 在“文件”菜单上,单击“全部保存”

    接下来为uspPlaceNewOrder定义单元测试逻辑。

编写 uspPlaceNewOrder 的 SQL Server 单元测试

  1. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspPlaceNewOrderTest,并确保相邻列表中突出显示的是 Test

    在执行此步骤后,可以为单元测试中的测试操作创建测试脚本。

  2. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    -- ssNoVersion unit test for Sales.uspPlaceNewOrder
    DECLARE @RC AS INT, @CustomerID AS INT, @Amount AS INT, @OrderDate AS DATETIME, @Status AS CHAR (1);
    
    DECLARE @CustomerName AS NVARCHAR (40);
    
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    SELECT @RC = 0,
           @CustomerID = 0,
           @CustomerName = N'Fictitious Customer',
           @Amount = 100,
           @OrderDate = getdate(),
           @Status = 'O';
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    
    -- place an order for that customer
    EXECUTE
        @RC = [Sales].[uspPlaceNewOrder]
        @CustomerID,
        @Amount,
        @OrderDate,
        @Status;
    
    -- verify that the YTDOrders value is correct.
    SELECT @RC = [YTDOrders]
    FROM [Sales].[Customer]
    WHERE [CustomerID] = @CustomerID;
    
    SELECT @RC AS RC;
    
  3. “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件”。

  4. “测试条件 ”窗格中,选择列表中的 标量值 ,然后选择“ 添加测试条件”。

  5. “属性” 窗口中,将 “所需的值” 属性设置为 100。

  6. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspPlaceNewOrderTest,并确保在相邻列表中突出显示 预测试

    在执行此步骤后,可以指定一些语句,以便将数据置于执行测试所需的状态。 对于此示例,必须先创建 Customer 记录,然后才能下订单。

  7. 选择 “单击此处”以创建 预测试脚本。

  8. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    /*
                                  Add Transact-SQL statements here that you want to run before
                                  the test script is run.
                                  */
    
    -- Add a customer for this test with the name 'Fictitious Customer'
    DECLARE @NewCustomerID AS INT, @CustomerID AS INT, @RC AS INT, @CustomerName AS NVARCHAR (40);
    
    SELECT @RC = 0,
           @NewCustomerID = 0,
           @CustomerID = 0,
           @CustomerName = N'Fictitious Customer';
    
    IF NOT EXISTS (SELECT *
                   FROM [Sales].[Customer]
                   WHERE CustomerName = @CustomerName)
        BEGIN
                    EXECUTE
                @NewCustomerID = [Sales].[uspNewCustomer]
                @CustomerName;
        END
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    -- delete any old records in the Orders table and clear out the YTD Sales/Orders fields
    
    DELETE [Sales].[Orders]
    WHERE [CustomerID] = @CustomerID;
    
    UPDATE [Sales].[Customer]
        SET YTDOrders = 0,
            YTDSales  = 0
    WHERE [CustomerID] = @CustomerID;
    
  9. 在“文件”菜单上,单击“全部保存”

    接下来,您为uspFillOrder创建单元测试。

编写 uspFillOrder 的 SQL Server 单元测试

  1. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspFillOrderTest,并确保在相邻列表中突出显示 测试

    在执行此步骤后,可以为单元测试中的测试操作创建测试脚本。

  2. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    -- ssNoVersion unit test for Sales.uspFillOrder
    DECLARE @RC AS INT, @CustomerID AS INT, @Amount AS INT, @FilledDate AS DATETIME, @Status AS CHAR (1);
    
    DECLARE @CustomerName AS NVARCHAR (40), @OrderID AS INT;
    
    SELECT @RC = 0,
           @CustomerID = 0,
           @OrderID = 0,
           @CustomerName = N'Fictitious Customer',
           @Amount = 100,
           @FilledDate = getdate(),
           @Status = 'O';
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    -- Get the most recently added order.
    
    SELECT @OrderID = MAX([OrderID])
    FROM [Sales].[Orders]
    WHERE [CustomerID] = @CustomerID;
    -- fill an order for that customer
    
    EXECUTE
        @RC = [Sales].[uspFillOrder]
        @OrderID,
        @FilledDate;
    -- verify that the YTDOrders value is correct.
    
    SELECT @RC = [YTDSales]
    FROM [Sales].[Customer]
    WHERE [CustomerID] = @CustomerID;
    
    SELECT @RC AS RC;
    
  3. “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件”。

  4. “测试条件 ”窗格中,选择列表中的 标量值 ,然后选择“ 添加测试条件”。

  5. “属性” 窗口中,将 “所需的值” 属性设置为 100。

  6. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspFillOrderTest,并确保在相邻列表中突出显示 预测试 。 在执行此步骤后,可以指定一些语句,以便将数据置于执行测试所需的状态。 对于此示例,您必须先创建 Customer 记录,然后才能下订单。

  7. 选择 “单击此处”以创建 预测试脚本。

  8. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    /*
                                  Add Transact-SQL statements here that you want to run before
                                  the test script is run.
                                  */
    BEGIN TRANSACTION
    -- Add a customer for this test with the name 'CustomerB'
    ;
    
    DECLARE @NewCustomerID AS INT, @RC AS INT, @CustomerName AS NVARCHAR (40);
    
    SELECT @RC = 0,
           @NewCustomerID = 0,
           @CustomerName = N'Fictitious Customer';
    
    IF NOT EXISTS (SELECT *
                   FROM [Sales].[Customer]
                   WHERE CustomerName = @CustomerName)
        BEGIN
                    EXECUTE
                @NewCustomerID = [Sales].[uspNewCustomer]
                @CustomerName;
        END
    
    DECLARE @CustomerID AS INT, @Amount AS INT, @OrderDate AS DATETIME, @Status AS CHAR (1);
    
    SELECT @RC = 0,
           @CustomerID = 0,
           @CustomerName = N'Fictitious Customer',
           @Amount = 100,
           @OrderDate = getdate(),
           @Status = 'O';
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    -- delete any old records in the Orders table and clear out the YTD Sales/Orders fields
    
    DELETE [Sales].[Orders]
    WHERE [CustomerID] = @CustomerID;
    
    UPDATE [Sales].[Customer]
        SET YTDOrders = 0,
            YTDSales  = 0
    WHERE [CustomerID] = @CustomerID;
    
    EXECUTE
        @RC = [Sales].[uspPlaceNewOrder]
        @CustomerID,
        @Amount,
        @OrderDate,
        @Status;
    
    COMMIT TRANSACTION;
    
    -- place an order for that customer
    
  9. 在“文件”菜单上,单击“全部保存”

为 uspShowOrderDetails 编写 SQL Server 单元测试

  1. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspShowOrderDetailsTest,并确保“ 测试 ”突出显示在相邻列表中。

    在执行此步骤后,可以为单元测试中的测试操作创建测试脚本。

  2. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    -- ssNoVersion unit test for Sales.uspFillOrder
    DECLARE @RC AS INT, @CustomerID AS INT, @Amount AS INT, @FilledDate AS DATETIME, @Status AS CHAR (1);
    
    DECLARE @CustomerName AS NVARCHAR (40), @OrderID AS INT;
    
    SELECT @RC = 0,
           @CustomerID = 0,
           @OrderID = 0,
           @CustomerName = N'Fictitious Customer',
           @Amount = 100,
           @FilledDate = getdate(),
           @Status = 'O';
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    -- fill an order for that customer
    
    EXECUTE
        @RC = [Sales].[uspShowOrderDetails]
        @CustomerID;
    
    SELECT @RC AS RC;
    
  3. “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件”。

  4. 在“ 测试条件 ”窗格中,选择列表中的 “预期架构 ”,然后选择“ 添加测试条件”。

  5. 在“ 属性” 窗口中的 “配置 ”属性中,选择浏览按钮('...')。

  6. “expectedSchemaCondition1 的配置” 对话框中,指定与您的数据库的连接。 例如,如果使用默认部署位置(即 LocalDB),则选择“ 新建连接 指定 ”(LocalDB)\Projects。 然后,选择该数据库的名称。

  7. 选择 “检索”。 (如有必要,请选择 “检索” ,直到看到数据。

    将执行单元测试的 Transact-SQL 主体,并且生成的架构将显示在对话框中。 由于未执行预测试代码,因此不会返回任何数据。 由于你只是验证架构而不是数据,所以这很好。

  8. 选择“确定”

    所需的架构与测试条件存储在一起。

  9. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspShowOrderDetailsTest,并确保预 测试 在相邻列表中突出显示。 在执行此步骤后,可以指定一些语句,以便将数据置于执行测试所需的状态。 对于此示例,必须先创建 Customer 记录,然后才能下订单。

  10. 选择 “单击此处”以创建 预测试脚本。

  11. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    /*
                                  Add Transact-SQL statements here to run before the test script is run.
                                  */
    BEGIN TRANSACTION
    -- Add a customer for this test with the name 'FictitiousCustomer'
    ;
    
    DECLARE @NewCustomerID AS INT, @RC AS INT, @CustomerName AS NVARCHAR (40);
    
    SELECT @RC = 0,
           @NewCustomerID = 0,
           @CustomerName = N'Fictitious Customer';
    
    IF NOT EXISTS (SELECT *
                   FROM [Sales].[Customer]
                   WHERE CustomerName = @CustomerName)
        BEGIN
                    EXECUTE
                @NewCustomerID = [Sales].[uspNewCustomer]
                @CustomerName;
        END
    
    DECLARE @CustomerID AS INT, @Amount AS INT, @OrderDate AS DATETIME, @Status AS CHAR (1);
    
    SELECT @RC = 0,
           @CustomerID = 0,
           @CustomerName = N'Fictitious Customer',
           @OrderDate = getdate(),
           @Status = 'O';
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    -- delete any old records in the Orders table and clear out the YTD Sales/Orders fields
    
    DELETE [Sales].[Orders]
    WHERE [CustomerID] = @CustomerID;
    
    UPDATE [Sales].[Customer]
        SET YTDOrders = 0,
            YTDSales  = 0
    WHERE [CustomerID] = @CustomerID;
    
    EXECUTE
        @RC = [Sales].[uspPlaceNewOrder]
        @CustomerID, 100,
        @OrderDate,
        @Status;
    
    EXECUTE
        @RC = [Sales].[uspPlaceNewOrder]
        @CustomerID, 50,
        @OrderDate,
        @Status;
    
    EXECUTE
        @RC = [Sales].[uspPlaceNewOrder]
        @CustomerID, 5,
        @OrderDate,
        @Status;
    
    COMMIT TRANSACTION;
    
    -- place 3 orders for that customer
    
  12. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspShowOrderDetailsTest,然后选择相邻列表中的 “测试 ”。

    您必须执行此操作,因为您要将校验和条件应用于测试,而不是预先测试。

  13. “测试条件 ”窗格中,选择列表中的 “数据校验和 ”,然后选择“ 添加测试条件”。

  14. 在“ 属性” 窗口中的 “配置 ”属性中,选择浏览按钮('...')。

  15. “checksumCondition1 的配置” 对话框中,指定与您的数据库的连接。

  16. 使用以下代码替换对话框(在“编辑连接”按钮下)中的 Transact-SQL:

    BEGIN TRANSACTION
    -- Add a customer for this test with the name 'CustomerB'
    ;
    
    DECLARE @NewCustomerID AS INT, @RC AS INT, @CustomerName AS NVARCHAR (40);
    
    SELECT @RC = 0,
           @NewCustomerID = 0,
           @CustomerName = N'Fictitious Customer';
    
    IF NOT EXISTS (SELECT *
                   FROM [Sales].[Customer]
                   WHERE CustomerName = @CustomerName)
        BEGIN
                    EXECUTE
                @NewCustomerID = [Sales].[uspNewCustomer]
                @CustomerName;
        END
    
    DECLARE @CustomerID AS INT, @Amount AS INT, @OrderDate AS DATETIME, @Status AS CHAR (1);
    
    SELECT @RC = 0,
           @CustomerID = 0,
           @CustomerName = N'Fictitious Customer',
           @OrderDate = getdate(),
           @Status = 'O';
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    -- delete any old records in the Orders table and clear out the YTD Sales/Orders fields
    
    DELETE [Sales].[Orders]
    WHERE [CustomerID] = @CustomerID;
    
    UPDATE [Sales].[Customer]
        SET YTDOrders = 0,
            YTDSales  = 0
    WHERE [CustomerID] = @CustomerID;
    
    EXECUTE
        @RC = [Sales].[uspPlaceNewOrder]
        @CustomerID, 100,
        @OrderDate,
        @Status;
    
    EXECUTE
        @RC = [Sales].[uspPlaceNewOrder]
        @CustomerID, 50,
        @OrderDate,
        @Status;
    
    EXECUTE
        @RC = [Sales].[uspPlaceNewOrder]
        @CustomerID, 5,
        @OrderDate,
        @Status;
    
    COMMIT TRANSACTION;
    
    DECLARE @FilledDate AS DATETIME;
    
    DECLARE @OrderID AS INT;
    
    SELECT @RC = 0,
           @CustomerID = 0,
           @OrderID = 0,
           @CustomerName = N'Fictitious Customer',
           @Amount = 100,
           @FilledDate = getdate(),
           @Status = 'O';
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    
    EXECUTE
        @RC = [Sales].[uspShowOrderDetails]
        @CustomerID;
    
    SELECT @RC AS RC;
    
    -- place 3 orders for that customer  -- ssNoVersion unit test for Sales.uspFillOrder  -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.  -- fill an order for that customer
    

    此代码将预先测试中的 Transact-SQL 代码与测试本身中的 Transact-SQL 结合在一起。 运行测试时,需要两者返回相同的结果。

  17. 选择 “检索”。 (如有必要,请选择 “检索” ,直到看到数据。

    将执行指定的 Transact-SQL,并且将针对返回的数据计算校验和。

  18. 选择“确定”

    计算出的校验和与测试条件存储在一起。 所需的校验和将显示在“数据校验和”测试条件的“值”列中。

  19. 在“文件”菜单上,单击“全部保存”

    此时,可以运行测试。

运行 SQL Server 单元测试

运行 SQL Server 单元测试

  1. “测试”菜单上,指向 Windows,然后在 Visual Studio 2010 或 Visual Studio 2012 中的“测试资源管理器”中选择“测试视图”。

  2. “测试视图 ”窗口中(Visual Studio 2010),选择工具栏上的 “刷新 ”以更新测试列表。 若要在“测试资源管理器”(Visual Studio 2012) 中查看测试列表,请生成解决方案。

    “测试视图”或“测试资源管理器”窗口中列出了在本演练前面创建的、并在其中添加了 Transact-SQL 语句和测试条件的测试。 名为 TestMethod1 的测试为空,在本演练中不使用。

  3. 右键单击 Sales_uspNewCustomerTest,然后选择“ 运行选择”。

    Visual Studio 使用指定的特权上下文连接到数据库并应用数据生成计划。 Visual Studio 先切换到执行上下文,再在测试中运行 Transact-SQL 脚本。 最后,Visual Studio 将针对在测试条件中指定的内容评估 Transact-SQL 脚本的结果,并在“测试结果”窗口中显示结果(通过或失败)。

  4. “测试结果” 窗口中查看结果。

    测试通过,这意味着 SELECT 语句在运行时返回一行。

  5. Sales_uspPlaceNewOrderTestSales_uspFillOrderTestSales_uspShowOrderDetailsTest测试重复步骤 3。 结果应如下所示:

    测试 预期结果
    Sales_uspPlaceNewOrderTest 通过
    Sales_uspShowOrderDetailsTest 通过
    Sales_uspFillOrderTest 失败并出现以下错误: ScalarValueCondition Condition (scalarValueCondition2) Failed: ResultSet 1 Row 1 Column 1: values do not match, actual '-100' expected '100'. 发生此错误是因为存储过程的定义包含一个小错误。

    接下来,更正错误并重新运行测试。

更正 Sales.uspFillOrder 中的错误

  1. 在数据库的“SQL Server 对象资源管理器”项目节点中,双击 uspFillOrder 存储过程以在 Transact-SQL 编辑器中打开其定义。

  2. 在定义中,找到以下 Transact-SQL 语句:

    UPDATE [Sales].[Customer]
        SET YTDSales = YTDSales - @Delta
    WHERE [CustomerID] = @CustomerID;
    
  3. 将语句中的SET子句更改为与以下语句匹配:

    UPDATE [Sales].[Customer]
        SET YTDSales = YTDSales + @Delta
    WHERE [CustomerID] = @CustomerID;
    
  4. 在“ 文件 ”菜单上,选择“ 保存uspFillOrder.sql

  5. 测试视图中,右键单击 Sales_uspFillOrderTest,然后选择“ 运行选择”。

    测试通过。

添加负面单元测试

您可以创建负面测试来验证测试在应失败时是否失败。 例如,如果您尝试取消已填充的订单,则该测试应失败。 在本演练的此部分,您将为 Sales.uspCancelOrder 存储过程创建一个负面单元测试。

若要创建和验证负面测试,您必须执行以下任务:

  • 更新存储过程以测试失败条件

  • 定义新单元测试

  • 修改单元测试的代码以指示它应失败

  • 运行单元测试

更新存储过程

  1. 在数据库的 SQL Server 对象资源管理器 项目节点 SimpleUnitTestDB 中,展开可编程性节点,展开存储过程节点,然后双击 uspCancelOrder

  2. 在 Transact-SQL 编辑器中,更新过程定义,使其与以下代码相匹配:

    CREATE PROCEDURE [Sales].[uspCancelOrder]
    @OrderID INT
    AS
    BEGIN
        DECLARE @Delta AS INT, @CustomerID AS INT, @PriorStatus AS CHAR (1);
        BEGIN TRANSACTION;
        BEGIN TRY
            IF (NOT EXISTS (SELECT [CustomerID]
                            FROM [Sales].[Orders]
                            WHERE [OrderID] = @OrderID))
                BEGIN
    -- Specify WITH LOG option so that the error is
    -- written to the application log.
                    RAISERROR ('That order does not exist.',
    -- Message text   16,
    -- severity   1
    -- state
    )
                        WITH LOG;
                END
            SELECT @Delta = [Amount],
                   @CustomerID = [CustomerID],
                   @PriorStatus = [Status]
            FROM [Sales].[Orders]
            WHERE [OrderID] = @OrderID;
            IF @PriorStatus <> 'O'
                BEGIN
    -- Specify WITH LOG option so that the error is
    -- written to the application log.
                    RAISERROR ('You can only cancel open orders.',
    -- Message text   16,
    -- Severity   1
    -- State
    )
                        WITH LOG;
                END
            ELSE
                BEGIN
    -- If we make it to here, then we can cancel the order. Update the status to 'X' first...
                    UPDATE [Sales].[Orders]
                        SET [Status] = 'X'
                    WHERE [OrderID] = @OrderID
    -- and then remove the amount from the YTDOrders for the customer
    ;
                    UPDATE [Sales].[Customer]
                        SET YTDOrders = YTDOrders - @Delta
                    WHERE [CustomerID] = @CustomerID;
                    COMMIT TRANSACTION;
                    RETURN 1;
    -- indicate success
                END
        END TRY
        BEGIN CATCH
            DECLARE @ErrorMessage AS NVARCHAR (4000);
            DECLARE @ErrorSeverity AS INT;
            DECLARE @ErrorState AS INT;
            SELECT @ErrorMessage = ERROR_MESSAGE(),
                   @ErrorSeverity = ERROR_SEVERITY(),
                   @ErrorState = ERROR_STATE();
            ROLLBACK;
            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
            RETURN 0;
        END CATCH
    END
    
    -- Use RAISERROR inside the CATCH block to return  -- error information about the original error that  -- caused execution to jump to the CATCH block.  -- Message text  -- Severity  -- State  -- indicate failure
    
  3. 在“ 文件 ”菜单上,选择“ 保存uspCancelOrder.sql

  4. F5 部署SimpleUnitTestDB

    将更新部署到 uspCancelOrder 存储过程。 您没有更改任何其他对象,因此仅更新了该存储过程。

    接下来,为此过程定义相关单元测试。

编写 uspCancelOrder 的 SQL Server 单元测试

  1. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspCancelOrderTest,确保< c1>测试 在相邻列表中被突出显示。

    在执行此步骤后,可以为单元测试中的测试操作创建测试脚本。

  2. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    -- ssNoVersion unit test for Sales.uspFillOrder
    DECLARE @RC AS INT, @CustomerID AS INT, @Amount AS INT, @FilledDate AS DATETIME, @Status AS CHAR (1);
    
    DECLARE @CustomerName AS NVARCHAR (40), @OrderID AS INT;
    
    SELECT @RC = 0,
           @CustomerID = 0,
           @OrderID = 0,
           @CustomerName = N'Fictitious Customer',
           @Amount = 100,
           @FilledDate = getdate(),
           @Status = 'O';
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    -- Get the most recently added order.
    
    SELECT @OrderID = MAX([OrderID])
    FROM [Sales].[Orders]
    WHERE [CustomerID] = @CustomerID;
    -- try to cancel an order for that customer that has already been filled
    
    EXECUTE
        @RC = [Sales].[uspCancelOrder]
        @OrderID;
    
    SELECT @RC AS RC;
    
  3. “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件 ”图标。

  4. “测试条件 ”窗格中,选择列表中的 标量值 ,然后选择 “添加测试条件 ”图标。

  5. 在“属性” 窗口中,将“预期值” 属性设为 0。

  6. 在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspCancelOrderTest,并确保在相邻列表中突出显示 预测试 。 在执行此步骤后,可以指定一些语句,以便将数据置于执行测试所需的状态。 对于此示例,必须先创建 Customer 记录,然后才能下订单。

  7. 选择 “单击此处”以创建 预测试脚本。

  8. 在 Transact-SQL 编辑器中更新 Transact-SQL 语句,使其与以下语句相匹配:

    /*
                                  Add Transact-SQL statements here to run before the test script is run.
                                  */
    BEGIN TRANSACTION
    -- Add a customer for this test with the name 'CustomerB'
    ;
    
    DECLARE @NewCustomerID AS INT, @RC AS INT, @CustomerName AS NVARCHAR (40);
    
    SELECT @RC = 0,
           @NewCustomerID = 0,
           @CustomerName = N'Fictitious Customer';
    
    IF NOT EXISTS (SELECT *
                   FROM [Sales].[Customer]
                   WHERE CustomerName = @CustomerName)
        BEGIN
                    EXECUTE
                @NewCustomerID = [Sales].[uspNewCustomer]
                @CustomerName;
        END
    
    DECLARE @CustomerID AS INT, @Amount AS INT, @OrderDate AS DATETIME, @FilledDate AS DATETIME, @Status AS CHAR (1), @OrderID AS INT;
    
    SELECT @RC = 0,
           @CustomerID = 0,
           @OrderID = 0,
           @CustomerName = N'Fictitious Customer',
           @Amount = 100,
           @OrderDate = getdate(),
           @FilledDate = getdate(),
           @Status = 'O';
    -- NOTE: Assumes that you inserted a Customer record with CustomerName='Fictitious Customer' in the pre-test script.
    
    SELECT @CustomerID = [CustomerID]
    FROM [Sales].[Customer]
    WHERE [CustomerName] = @CustomerName;
    -- delete any old records in the Orders table and clear out the YTD Sales/Orders fields
    
    DELETE [Sales].[Orders]
    WHERE [CustomerID] = @CustomerID;
    
    UPDATE [Sales].[Customer]
        SET YTDOrders = 0,
            YTDSales  = 0
    WHERE [CustomerID] = @CustomerID;
    
    EXECUTE
        @OrderID = [Sales].[uspPlaceNewOrder]
        @CustomerID,
        @Amount,
        @OrderDate,
        @Status;
    
    EXECUTE
        @RC = [Sales].[uspFillOrder]
        @OrderID,
        @FilledDate;
    
    COMMIT TRANSACTION;
    
    -- place an order for that customer  -- fill the order for that customer
    
  9. 在“文件”菜单上,单击“全部保存”

    此时,可以运行测试。

运行 SQL Server 单元测试

  1. “测试视图”中,右键单击 Sales_uspCancelOrderTest,然后选择“ 运行选择”。

  2. “测试结果” 窗口中查看结果。

    测试失败并显示以下错误:

    Test method TestProject1.SqlServerUnitTests1.Sales_uspCancelOrderTest threw exception: System.Data.SqlClient.SqlException: You can only cancel open orders.
    

    接下来,修改代码以指示应出现异常。

修改单元测试的代码

  1. 解决方案资源管理器中,展开 TestProject1,右键单击 SqlServerUnitTests1.cs,然后选择“ 查看代码”。

  2. 在代码编辑器中,导航至 Sales_uspCancelOrderTest 方法。 修改此方法的属性,使其与以下代码相匹配:

    [TestMethod(), ExpectedSqlException(Severity=16, MatchFirstError=false, State=1)]
    public void Sales_uspCancelOrderTest()
    

    您指定希望看到特定异常。 您可以选择指定特定错误编号。 如果未添加此属性,单元测试将失败,并在“测试结果”窗口中显示一条消息

  3. 在“文件”菜单上,选择“保存SqlServerUnitTests1.cs”。

    接下来,重新运行单元测试以验证它是否按预期失败。

重新运行 SQL Server 单元测试

  1. “测试视图”中,右键单击 Sales_uspCancelOrderTest,然后选择“ 运行选择”。

  2. “测试结果” 窗口中查看结果。

    测试通过,这意味着过程在应失败时失败。