在本演练中,将创建一个 SQL Server 单元测试,该测试验证多个存储过程的行为。 创建 SQL Server 单元测试可帮助确定可能会引发不正确的应用程序行为的代码缺陷。 可以将 SQL Server 单元测试和应用程序测试作为一组自动执行的测试的一部分来运行。
在本演练中,您将执行下列任务:
在某个单元测试检测到存储过程中的错误后,请更正该错误并重新运行测试。
先决条件
若要完成本演练,您必须能够连接到有权在其上创建和部署数据库的数据库服务器(或 LocalDB 数据库)。 有关详细信息,请参阅 执行 Visual Studio 的数据库功能所需的权限。
创建包含数据库架构的脚本
在“ 文件 ”菜单上,指向“ 新建”,然后选择“ 文件”。
此时将显示 “新建文件” 对话框。
在 “类别 ”列表中,选择“ 常规 ”(如果尚未突出显示)。
在 “模板” 列表中,选择 “Sql 文件”,然后选择“ 打开”。
将打开 Transact-SQL 编辑器。
复制以下 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保存文件。 记下该位置,因为在下一个过程中必须使用此脚本。
在 “文件 ”菜单上,选择“ 关闭解决方案”。
接下来,创建一个数据库项目并从已创建的脚本导入架构。
创建数据库项目并导入架构
创建一个数据库项目
在 “文件 ”菜单上,指向“ 新建”,然后选择“ 项目”。
将显示“新建项目”对话框。
在“已安装的模板”下,选择“SQL Server”节点,然后选择“SQL Server 数据库项目”。
在 Name 中,键入
SimpleUnitTestDB。如果尚未选中 为解决方案创建目录 的复选框,请选中该复选框。
如果尚未清除,请清除“ 添加到源代码管理 ”复选框,然后选择“ 确定”。
将创建数据库项目,并且它将显示在 “解决方案资源管理器” 中。 接下来,从脚本导入数据库架构。
从脚本导入数据库架构
在“项目”菜单上,选择“导入”,然后选择“脚本”(*.sql)。
阅读欢迎页后,选择 “下一步 ”。
选择 “浏览”,然后转到保存
.sql文件的目录。双击
.sql该文件,然后选择“ 完成”。将导入脚本,并且该脚本中定义的对象将添加到您的数据库项目中。
查看摘要,然后选择“ 完成 ”以完成作。
注意
Sales.uspFillOrder 过程包含你稍后在此过程中发现和更正的有意编码错误。
检查生成的项目
在 “解决方案资源管理器” 中,检查已导入到项目中的脚本文件。
在“SQL Server 对象资源管理器”中,在“项目”节点中查看该数据库。
部署到 LocalDB
默认情况下,按 F5 时,将数据库部署到 LocalDB 数据库(或发布)。 您可以通过转到项目属性页的“调试”选项卡并更改连接字符串,更改数据库位置。
创建 SQL Server 单元测试
为存储过程创建 SQL Server 单元测试
在 SQL Server 对象资源管理器中,展开
SimpleUnitTestDB项目节点,然后展开可编程性,接着展开存储过程节点。右键单击其中一个存储过程,然后选择“ 创建单元测试 ”以显示“ 创建单元测试 ”对话框。
选中所有五个存储过程对应的复选框:“Sales.uspCancelOrder” 、“Sales.uspFillOrder” 、“Sales.uspNewCustomer” 、“Sales.uspPlaceNewOrder” 和“Sales.uspShowOrderDetails” 。
在 “项目 ”下拉列表中,选择“ 创建新的 C# 测试项目”。
接受项目名称和类名的默认名称,然后选择“ 确定”。
在测试配置对话框的 “使用以下数据连接执行单元测试”中,指定与您在本演练前面部署的数据库的连接。 例如,如果使用默认部署位置(即 LocalDB),则选择“ 新建连接 指定 ”(LocalDB)\Projects。 然后,选择该数据库的名称。 然后,选择“ 确定 ”关闭 “连接属性 ”对话框。
注意
如果必须测试具有有限权限的视图或存储过程,则通常会在此步骤中指定该连接。 然后,指定具有更广泛权限的辅助连接来验证测试。 如果你有辅助连接,则应将该用户添加到数据库项目,并在预部署脚本中为该用户创建登录名。
在测试配置对话框的 “部署” 部分中,选中 “运行单元测试前,自动部署该数据库项目” 复选框。
在 数据库项目中,选择
SimpleUnitTestDB.sqlproj。在 部署配置中,选择“ 调试”。
在 SQL Server 单元测试过程中,可能还需要生成测试数据。 在本演练中,将跳过此步骤,因为测试会创建自己的数据。
选择“确定”。
将生成测试项目,并且将显示 SQL Server 单元测试设计器。 接下来,更新单元测试 Transact-SQL 脚本中的测试逻辑。
定义测试逻辑
此基本数据库有两个表, Customer 以及 Order。 可以使用以下存储过程更新数据库:
| 存储过程 | Description |
|---|---|
uspNewCustomer |
此存储过程将记录添加到 Customer 表中,该记录将客户的 YTDOrders 列 YTDSales 设置为零。 |
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 美元。 填充该订单。 验证YTDOrders和YTDSales的金额是否均为50。uspShowOrderDetails:对于拥有CustomerID1 的客户,请分别订购 100 美元、50 美元和 5 美元。 验证是否uspShowOrderDetails返回正确的列数,结果集是否具有预期的校验和。
注意
为获得一组完整的 SQL Server 单元测试,通常需要验证其他列的设置是否正确。 为了保持本演练内容的简洁,未描述如何验证uspCancelOrder的行为。
为 uspNewCustomer 编写 SQL Server 单元测试
在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspNewCustomerTest,并确保 测试在相邻 列表中突出显示。
在执行上一步后,可以为单元测试中的测试操作创建测试脚本。
在 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];在 “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件 ”图标(红色 X)。
在 “测试条件 ”窗格中,选择列表中的 行计数 ,然后选择 “添加测试条件 ”图标(绿色 +)。
打开 “属性” 窗口(选择测试条件并按 F4),并将 “行计数 ”属性设置为 1。
在“文件”菜单上,单击“全部保存”。
接下来为
uspPlaceNewOrder定义单元测试逻辑。
编写 uspPlaceNewOrder 的 SQL Server 单元测试
在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspPlaceNewOrderTest,并确保相邻列表中突出显示的是 Test。
在执行此步骤后,可以为单元测试中的测试操作创建测试脚本。
在 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;在 “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件”。
在 “测试条件 ”窗格中,选择列表中的 标量值 ,然后选择“ 添加测试条件”。
在 “属性” 窗口中,将 “所需的值” 属性设置为 100。
在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspPlaceNewOrderTest,并确保在相邻列表中突出显示 预测试 。
在执行此步骤后,可以指定一些语句,以便将数据置于执行测试所需的状态。 对于此示例,必须先创建
Customer记录,然后才能下订单。选择 “单击此处”以创建 预测试脚本。
在 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;在“文件”菜单上,单击“全部保存”。
接下来,您为
uspFillOrder创建单元测试。
编写 uspFillOrder 的 SQL Server 单元测试
在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspFillOrderTest,并确保在相邻列表中突出显示 测试 。
在执行此步骤后,可以为单元测试中的测试操作创建测试脚本。
在 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;在 “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件”。
在 “测试条件 ”窗格中,选择列表中的 标量值 ,然后选择“ 添加测试条件”。
在 “属性” 窗口中,将 “所需的值” 属性设置为 100。
在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspFillOrderTest,并确保在相邻列表中突出显示 预测试 。 在执行此步骤后,可以指定一些语句,以便将数据置于执行测试所需的状态。 对于此示例,您必须先创建 Customer 记录,然后才能下订单。
选择 “单击此处”以创建 预测试脚本。
在 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在“文件”菜单上,单击“全部保存”。
为 uspShowOrderDetails 编写 SQL Server 单元测试
在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspShowOrderDetailsTest,并确保“ 测试 ”突出显示在相邻列表中。
在执行此步骤后,可以为单元测试中的测试操作创建测试脚本。
在 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;在 “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件”。
在“ 测试条件 ”窗格中,选择列表中的 “预期架构 ”,然后选择“ 添加测试条件”。
在“ 属性” 窗口中的 “配置 ”属性中,选择浏览按钮('...')。
在 “expectedSchemaCondition1 的配置” 对话框中,指定与您的数据库的连接。 例如,如果使用默认部署位置(即 LocalDB),则选择“ 新建连接 指定 ”(LocalDB)\Projects。 然后,选择该数据库的名称。
选择 “检索”。 (如有必要,请选择 “检索” ,直到看到数据。
将执行单元测试的 Transact-SQL 主体,并且生成的架构将显示在对话框中。 由于未执行预测试代码,因此不会返回任何数据。 由于你只是验证架构而不是数据,所以这很好。
选择“确定”。
所需的架构与测试条件存储在一起。
在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspShowOrderDetailsTest,并确保预 测试 在相邻列表中突出显示。 在执行此步骤后,可以指定一些语句,以便将数据置于执行测试所需的状态。 对于此示例,必须先创建
Customer记录,然后才能下订单。选择 “单击此处”以创建 预测试脚本。
在 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在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspShowOrderDetailsTest,然后选择相邻列表中的 “测试 ”。
您必须执行此操作,因为您要将校验和条件应用于测试,而不是预先测试。
在 “测试条件 ”窗格中,选择列表中的 “数据校验和 ”,然后选择“ 添加测试条件”。
在“ 属性” 窗口中的 “配置 ”属性中,选择浏览按钮('...')。
在 “checksumCondition1 的配置” 对话框中,指定与您的数据库的连接。
使用以下代码替换对话框(在“编辑连接”按钮下)中的 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 结合在一起。 运行测试时,需要两者返回相同的结果。
选择 “检索”。 (如有必要,请选择 “检索” ,直到看到数据。
将执行指定的 Transact-SQL,并且将针对返回的数据计算校验和。
选择“确定”。
计算出的校验和与测试条件存储在一起。 所需的校验和将显示在“数据校验和”测试条件的“值”列中。
在“文件”菜单上,单击“全部保存”。
此时,可以运行测试。
运行 SQL Server 单元测试
运行 SQL Server 单元测试
在“测试”菜单上,指向 Windows,然后在 Visual Studio 2010 或 Visual Studio 2012 中的“测试资源管理器”中选择“测试视图”。
在 “测试视图 ”窗口中(Visual Studio 2010),选择工具栏上的 “刷新 ”以更新测试列表。 若要在“测试资源管理器”(Visual Studio 2012) 中查看测试列表,请生成解决方案。
“测试视图”或“测试资源管理器”窗口中列出了在本演练前面创建的、并在其中添加了 Transact-SQL 语句和测试条件的测试。 名为 TestMethod1 的测试为空,在本演练中不使用。
右键单击 Sales_uspNewCustomerTest,然后选择“ 运行选择”。
Visual Studio 使用指定的特权上下文连接到数据库并应用数据生成计划。 Visual Studio 先切换到执行上下文,再在测试中运行 Transact-SQL 脚本。 最后,Visual Studio 将针对在测试条件中指定的内容评估 Transact-SQL 脚本的结果,并在“测试结果”窗口中显示结果(通过或失败)。
在 “测试结果” 窗口中查看结果。
测试通过,这意味着
SELECT语句在运行时返回一行。对
Sales_uspPlaceNewOrderTest和Sales_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 中的错误
在数据库的“SQL Server 对象资源管理器”项目节点中,双击 uspFillOrder 存储过程以在 Transact-SQL 编辑器中打开其定义。
在定义中,找到以下 Transact-SQL 语句:
UPDATE [Sales].[Customer] SET YTDSales = YTDSales - @Delta WHERE [CustomerID] = @CustomerID;将语句中的
SET子句更改为与以下语句匹配:UPDATE [Sales].[Customer] SET YTDSales = YTDSales + @Delta WHERE [CustomerID] = @CustomerID;在“ 文件 ”菜单上,选择“ 保存uspFillOrder.sql。
在 测试视图中,右键单击 Sales_uspFillOrderTest,然后选择“ 运行选择”。
测试通过。
添加负面单元测试
您可以创建负面测试来验证测试在应失败时是否失败。 例如,如果您尝试取消已填充的订单,则该测试应失败。 在本演练的此部分,您将为 Sales.uspCancelOrder 存储过程创建一个负面单元测试。
若要创建和验证负面测试,您必须执行以下任务:
更新存储过程以测试失败条件
定义新单元测试
修改单元测试的代码以指示它应失败
运行单元测试
更新存储过程
在数据库的 SQL Server 对象资源管理器 项目节点
SimpleUnitTestDB中,展开可编程性节点,展开存储过程节点,然后双击uspCancelOrder。在 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在“ 文件 ”菜单上,选择“ 保存uspCancelOrder.sql。
按 F5 部署
SimpleUnitTestDB。将更新部署到
uspCancelOrder存储过程。 您没有更改任何其他对象,因此仅更新了该存储过程。接下来,为此过程定义相关单元测试。
编写 uspCancelOrder 的 SQL Server 单元测试
在 SQL Server 单元测试设计器的导航栏中,选择
Sales_uspCancelOrderTest ,确保< c1>测试 在相邻列表中被突出显示。在执行此步骤后,可以为单元测试中的测试操作创建测试脚本。
在 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;在 “测试条件 ”窗格中,选择“不确定的测试条件”,然后选择“ 删除测试条件 ”图标。
在 “测试条件 ”窗格中,选择列表中的 标量值 ,然后选择 “添加测试条件 ”图标。
在“属性” 窗口中,将“预期值” 属性设为 0。
在 SQL Server 单元测试设计器的导航栏中,选择 Sales_uspCancelOrderTest,并确保在相邻列表中突出显示 预测试 。 在执行此步骤后,可以指定一些语句,以便将数据置于执行测试所需的状态。 对于此示例,必须先创建
Customer记录,然后才能下订单。选择 “单击此处”以创建 预测试脚本。
在 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在“文件”菜单上,单击“全部保存”。
此时,可以运行测试。
运行 SQL Server 单元测试
在 “测试视图”中,右键单击 Sales_uspCancelOrderTest,然后选择“ 运行选择”。
在 “测试结果” 窗口中查看结果。
测试失败并显示以下错误:
Test method TestProject1.SqlServerUnitTests1.Sales_uspCancelOrderTest threw exception: System.Data.SqlClient.SqlException: You can only cancel open orders.接下来,修改代码以指示应出现异常。
修改单元测试的代码
在 解决方案资源管理器中,展开 TestProject1,右键单击 SqlServerUnitTests1.cs,然后选择“ 查看代码”。
在代码编辑器中,导航至 Sales_uspCancelOrderTest 方法。 修改此方法的属性,使其与以下代码相匹配:
[TestMethod(), ExpectedSqlException(Severity=16, MatchFirstError=false, State=1)] public void Sales_uspCancelOrderTest()您指定希望看到特定异常。 您可以选择指定特定错误编号。 如果未添加此属性,单元测试将失败,并在“测试结果”窗口中显示一条消息
在“文件”菜单上,选择“保存SqlServerUnitTests1.cs”。
接下来,重新运行单元测试以验证它是否按预期失败。
重新运行 SQL Server 单元测试
在 “测试视图”中,右键单击 Sales_uspCancelOrderTest,然后选择“ 运行选择”。
在 “测试结果” 窗口中查看结果。
测试通过,这意味着过程在应失败时失败。