逐步解說:建立和執行資料庫單元測試
在這個逐步解說中,您會建立「資料庫單元測試」(Database Unit Test),以便驗證許多預存程序的行為。 建立資料庫單元測試可協助您識別可能導致應用程式行為不正確的程式碼缺失。 您可以在自動化的測試套件中執行資料庫單元測試和應用程式測試。
在這個逐步解說中,您會執行下列工作:
建立包含資料庫結構描述的指令碼
建立資料庫專案並匯入該結構描述
將資料庫專案部署至隔離的開發環境
建立資料庫單元測試
定義測試邏輯
執行資料庫單元測試
加入負面單元測試
在其中一個單元測試偵測到預存程序中的錯誤之後,您就要更正該錯誤並重新執行測試。
必要條件
若要完成這個逐步解說,您必須能夠連接到有權建立和部署資料庫的資料庫伺服器。 如需詳細資訊,請參閱 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/2020')); 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/2020')); GO PRINT N'Creating Sales.uspCancelOrder...'; GO CREATE PROCEDURE [Sales].[uspCancelOrder] @OrderID INT AS BEGIN DECLARE @Delta INT, @CustomerID 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 INT, @CustomerID 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); SELECT 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 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
按一下 [檔案] 功能表上的 [另存 SqlQuery_1.sql]。
[另存新檔] 對話方塊隨即出現。
在 [物件名稱] 中,輸入 SampleImportScript.sql。
您可以將此檔案儲存至電腦上的任何位置。 請記下這個位置,因為您必須在下一個程序中使用這個指令碼。
按一下 [儲存]。
在 [檔案] 功能表上,按一下 [關閉方案]。
接著,您要建立資料庫專案,並從您已建立的指令碼匯入結構描述。
建立資料庫專案並匯入結構描述
若要建立資料庫專案
在 [檔案] 功能表中,指向 [新增],然後按一下 [專案]。
[新增專案] 對話方塊隨即出現。
展開 [已安裝的範本] 底下的 [資料庫] 節點,然後按一下 [SQL Server]。
注意事項 如果您正在使用 Visual Studio Professional,請查看 [已安裝的範本],依序展開 [資料庫] 節點、[SQL Server] 節點,然後按一下 [進階]。
在範本清單中,按一下 [SQL Server 2008 資料庫專案]。
注意事項 如果您想要將資料庫部署到不同的資料庫版本,請改為選擇對應於目標伺服器的範本。
在 [名稱] 中,輸入 SimpleUnitTestDB。
選取 [為方案建立目錄] 核取方塊 (若尚未選取)。
清除 [加入至原始檔控制] 核取方塊 (若尚未清除),然後按一下 [確定]。
資料庫專案就會在 [方案總管] 中建立並出現。 接下來,您要從指令碼匯入資料庫結構描述。
若要從指令碼匯入資料庫結構描述
按一下 [專案] 功能表上的 [匯入指令碼]。
閱讀完 [歡迎] 頁面之後,按 [下一步]。
按一下 [瀏覽],並且指出您儲存 SampleImportScript.sql 檔案的路徑。
按兩下 SampleImportScript.sql 檔案,然後按一下 [完成]。
如此就會匯入指令碼,而且該指令碼中定義的物件會加入至資料庫專案。
檢閱摘要,然後按一下 [完成] 以完成作業。
注意事項 Sales.uspFillOrder 程序包含刻意設計的程式碼錯誤,您之後將在這個程序中找到並更正此錯誤。
若要檢查產生的專案
在 [方案總管] 中,展開 [結構描述物件] 子節點。
瀏覽在階層架構中 [結構描述物件] 節點底下的子節點。
[方案總管] 包含定義資料庫物件的檔案。
按一下 [檢視] 功能表上的 [資料庫結構描述檢視]。
展開 [結構描述檢視] 中的 [SimpleUnitTestDB] 節點。
瀏覽在階層架構中 [SimpleUnitTestDB] 節點底下的子節點。
[結構描述檢視] 包含物件,這些物件是在 [方案總管] 出現的檔案中定義的。
部署至隔離的開發環境
接下來,您會部署專案,以便建立具有已匯入結構描述但不包含任何資料的資料庫。 您會在「隔離的開發環境」(Isolated Development Environment) (或沙箱) 中建立這個資料庫,以便在不受其他作業干擾的情況下開發和測試資料庫。
若要設定並建置資料庫專案
在 [方案總管] 中,按一下資料庫專案 SimpleUnitTestDB。
按一下 [專案] 功能表上的 [SimpleUnitTestDB 屬性]。
此專案的屬性對話方塊隨即出現。
按一下 [部署] 索引標籤。
按一下 [為下列項目設定部署設定] 清單中的 [我的隔離開發環境]。 您可以針對隔離的開發環境進行設定,以便使用不同的部署設定,讓這些設定有別於其他環境 (例如開發用或實際執行伺服器) 所使用的部署設定。
在 [部署動作] 清單中,按一下 [建立部署指令碼 (.sql) 並部署到資料庫]。
在 [目標資料庫設定] 中,按一下 [編輯]。
[連接屬性] 對話方塊隨即出現。
針對您要建立的資料庫設定其連接屬性,然後按一下 [確定]。
正確的連接字串就會出現在 [目標連接] 方塊中。
警告
您應該在測試伺服器、程式開發伺服器或本機電腦上建立資料庫。 您不應該指定實際執行伺服器。
在 [目標資料庫名稱] 中,輸入 SimpleUnitTestDB。
按一下 [部署組態檔] 旁邊的 [編輯]。
清除 [如果可能發生資料遺失,則封鎖累加部署] 核取方塊。
注意事項 在這個逐步解說中,您將針對部署成資料庫單元測試一部分的空白資料庫測試預存程序。 您不需要保留任何現有的資料,因為您將在隔離的開發環境中測試預存程序。
在 [檔案] 功能表上按一下 [全部儲存]。
在 [建置] 功能表上,按一下 [建置方案]。
您剛才設定的屬性會決定部署指令碼的建置方式。 建置的狀態會出現在 [輸出] 視窗中,而且 [組建: 1 成功或最新狀態] 應該會出現在最後一行。
注意事項 如果 [輸出] 視窗並未出現,請開啟 [檢視] 功能表,然後按一下 [輸出]。
若要部署資料庫專案
在 [方案總管] 中,按一下資料庫專案 SimpleUnitTestDB。
按一下 [建置] 功能表上的 [部署 SimpleUnitTestDB]。
警告
您應該針對測試伺服器、程式開發伺服器或本機電腦執行這項部署, 您不應該指定實際執行伺服器。
資料庫專案即會部署到新的資料庫。 部署的狀態會出現在 [輸出] 視窗中,而且 [部署: 1 成功] 應該會出現在最後一行。 您可能會定義「資料產生計劃」(Data Generation Plan),在資料庫中建立測試資料。 在這個逐步解說中,您要測試非常簡單的資料庫,因此不需要產生資料。
建立資料庫單元測試
若要為預存程序建立資料庫單元測試
按一下 [檢視] 功能表上的 [資料庫結構描述檢視]。
在 [結構描述檢視] 中,依序展開 [結構描述] 節點、[Sales] 節點、[可程式性] 節點和 [預存程序] 節點。
以滑鼠右鍵按一下 [uspNewCustomer] 預存程序,然後按一下 [建立單元測試]。
[建立單元測試] 對話方塊隨即出現。
選取總共五個預存程序的核取方塊:[Sales.uspCancelOrder]、[Sales.uspFillOrder]、[Sales.uspNewCustomer]、[Sales.uspPlaceNewOrder] 和 [Sales.uspShowOrderDetails]。
在 [專案] 中,按一下 [建立新的 Visual C# 測試專案]。
接受專案名稱和類別名稱的預設名稱,然後按一下 [確定]。
[專案 'TestProject1' 組態] 對話方塊隨即出現。
在 [使用下列資料連接來執行單元測試] 中,指定您之前在這個逐步解說中部署的資料庫連接。
注意事項 如果您必須測試具有限制權限的檢視表或預存程序,通常會在這個步驟中指定該連接。 然後,您要設定第二次連接 (權限較廣) 來驗證測試。 如果您有實作第二次連接,就應該將該使用者加入至資料庫專案,然後在預先部署指令碼中建立該使用者的登入。
在 [部署] 中,選取 [執行單元測試前自動部署資料庫專案] 核取方塊。
在 [資料庫專案] 中,按一下 [SimpleUnitTestDB.dbproj]。
在 [部署組態檔] 中,按一下 [偵錯]。
您可能也會在資料庫單元測試中產生測試資料。 在這個逐步解說中,您將略過該步驟,因為這些測試會建立自己的資料。
按一下 [確定]。
測試專案隨即建置,並且出現 [資料庫單元測試設計工具]。 接下來,您將在單元測試的 Transact-SQL 指令碼中更新測試邏輯。
定義測試邏輯
這個非常簡單的資料庫具有兩個資料表:Customer 和 Order。 您可以使用下列預存程序來更新資料庫:
uspNewCustomer - 這個預存程序會將一筆資料錄加入至 Customer 資料表,以便將客戶的 YTDOrders 和 YTDSales 資料行設定為零。
uspPlaceNewOrder - 這個預存程序會將一筆資料錄加入至指定之客戶的 Orders 資料表,並且在 Customer 資料表中更新對應資料錄的 YTDOrders 值。
uspFillOrder - 這個預存程序會更新 Orders 資料表中的資料錄 (將其狀態從 'O' 變更為 'F'),並且在 Customer 資料表中遞增對應資料錄的 YTDSales 金額。
uspCancelOrder - 這個預存程序會更新 Orders 資料表中的資料錄 (將其狀態從 'O' 變更為 'X'),並且在 Customer 資料表中遞減對應資料錄的 YTDOrders 金額。
uspShowOrderDetails - 這個預存程序會聯結 Orders 資料表與 Custom 資料表,並且顯示特定客戶的資料錄。
注意事項 |
---|
這則範例將說明如何建立簡單的資料庫單元測試。 在實際使用的資料庫中,您可以針對特定客戶加總狀態為 'O' 或 'F' 之所有訂單的總金額。 這個逐步解說中的程序也不包含任何錯誤處理。 例如,它們不會防止您針對已經填寫的訂單呼叫 uspFillOrder。 |
這些測試會假設資料庫在初始狀態下啟動。 您將建立驗證下列條件的測試:
uspNewCustomer - 驗證 Customer 資料表在您執行預存程序之後是否包含一個資料列。
uspPlaceNewOrder - 針對 CustomerID 為 1 的客戶,下 $100 的訂單。 驗證該客戶的 YTDOrders 金額是否為 100,而且 YTDSales 金額是否為零。
uspFillOrder - 針對 CustomerID 為 1 的客戶,下 $50 的訂單。 填寫該筆訂單。 驗證 YTDOrders 和 YTDSales 金額是否都為 50。
uspShowOrderDetails - 針對 CustomerID 為 1 的客戶,分別下 $100、$50 和 $5 的訂單。 驗證 uspShowOrderDetails 是否傳回正確的資料行數目,而且結果集是否具有預期的總和檢查碼。
注意事項 |
---|
就一套完整的資料庫單元測試而言,您通常會驗證其他資料行的設定是否正確。 為了讓這個逐步解說的大小容易管理,它不會描述如何驗證 uspCancelOrder 的行為。 |
若要為 uspNewCustomer 撰寫資料庫單元測試
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspNewCustomerTest],並確定旁邊的清單中反白顯示了 [測試]。
執行上一個步驟之後,您就可以針對單元測試中的測試動作建立測試指令碼。
在 Transact-SQL 編輯器中更新 Transact-SQL 陳述式,以便符合下列陳述式:
-- database 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)。
在 [測試條件] 窗格的清單中,按一下 [資料列計數],然後按一下 [加入測試條件] (+)。
在 [屬性] 視窗中,將 [資料列計數] 屬性設為 1。
在 [檔案] 功能表上按一下 [全部儲存]。
接下來,您會定義 uspPlaceNewOrder 的單元測試邏輯。
若要為 uspPlaceNewOrder 撰寫資料庫單元測試
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspPlaceNewOrderTest],並確定旁邊的清單中反白顯示了 [測試]。
執行這個步驟之後,您就可以針對單元測試中的測試動作建立測試指令碼。
在 Transact-SQL 編輯器中更新 Transact-SQL 陳述式,以便符合下列陳述式:
-- database 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); 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; -- 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
在 [測試條件] 窗格中,按一下結果不明的測試條件,然後按一下 [刪除測試條件] (x)。
在 [測試條件] 窗格的清單中,按一下 [純量值],然後按一下 [加入測試條件] (+)。
在 [屬性] 視窗中,將 [需要的值] 屬性設為 100。
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspPlaceNewOrderTest],並確定旁邊的清單中反白顯示了 [測試前]。
執行這個步驟之後,您就可以指定陳述式,以便讓資料進入執行測試所需的狀態。 在這個範例中,您必須先建立客戶記錄,然後才能下訂單。
按一下 [按一下此處以建立] 建立測試前指令碼。
在 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 from [Sales].[Orders] WHERE [CustomerID] = @CustomerID; UPDATE [Sales].[Customer] SET YTDOrders = 0, YTDSales = 0 WHERE [CustomerID] = @CustomerID;
在 [檔案] 功能表上按一下 [全部儲存]。
接下來,您會建立 uspFillOrder 的單元測試。
若要為 uspFillOrder 撰寫資料庫單元測試
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspFillOrderTest],並確定旁邊的清單中反白顯示了 [測試]。
執行這個步驟之後,您就可以針對單元測試中的測試動作建立測試指令碼。
在 Transact-SQL 編輯器中更新 Transact-SQL 陳述式,以便符合下列陳述式:
-- database 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;
在 [測試條件] 窗格中,按一下結果不明的測試條件,然後按一下 [刪除測試條件] (x)。
在 [測試條件] 窗格的清單中,按一下 [純量值],然後按一下 [加入測試條件] (+)。
在 [屬性] 視窗中,將 [需要的值] 屬性設為 100。
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspFillOrderTest],並確定旁邊的清單中反白顯示了 [測試前]。 執行這個步驟之後,您就可以指定陳述式,以便讓資料進入執行測試所需的狀態。 在這個範例中,您必須先建立客戶記錄,然後才能下訂單。
按一下 [按一下此處以建立] 建立測試前指令碼。
在 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 from [Sales].[Orders] WHERE [CustomerID] = @CustomerID; UPDATE [Sales].[Customer] SET YTDOrders = 0, YTDSales = 0 WHERE [CustomerID] = @CustomerID; -- place an order for that customer EXECUTE @RC = [Sales].[uspPlaceNewOrder] @CustomerID, @Amount, @OrderDate, @Status; COMMIT TRANSACTION
在 [檔案] 功能表上按一下 [全部儲存]。
此時,您已經準備好執行測試。
若要為 uspShowOrderDetails 撰寫資料庫單元測試
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspShowOrderDetailsTest],並確定旁邊的清單中反白顯示了 [測試]。
執行這個步驟之後,您就可以針對單元測試中的測試動作建立測試指令碼。
在 Transact-SQL 編輯器中更新 Transact-SQL 陳述式,以便符合下列陳述式:
-- database 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;
在 [測試條件] 窗格中,按一下結果不明的測試條件,然後按一下 [刪除測試條件] (x)。
在 [測試條件] 窗格的清單中,按一下 [預期的結構描述],然後按一下 [加入測試條件] (+)。
在 [屬性] 視窗的 [組態] 屬性中,按一下瀏覽按鈕 ('...')。
在 [expectedSchemaCondition1 的組態] 對話方塊中,指定資料庫的連接。
按一下 [擷取]。
如此就會執行單元測試的 Transact-SQL 主體,而且產生的結構描述會出現在對話方塊中。 因為沒有執行測試前程式碼,所以不會傳回任何資料。 因為您只要驗證結構描述而非資料,所以這樣沒關係。
按一下 [確定]。
預期的結構描述就會與測試條件一起儲存。
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspShowOrderDetailsTest],並確定旁邊的清單中反白顯示了 [測試前]。 執行這個步驟之後,您就可以指定陳述式,以便讓資料進入執行測試所需的狀態。 在這個範例中,您必須先建立客戶記錄,然後才能下訂單。
按一下 [按一下此處以建立] 建立測試前指令碼。
在 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 '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 from [Sales].[Orders] WHERE [CustomerID] = @CustomerID; UPDATE [Sales].[Customer] SET YTDOrders = 0, YTDSales = 0 WHERE [CustomerID] = @CustomerID; -- place 3 orders for that customer 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
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [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 from [Sales].[Orders] WHERE [CustomerID] = @CustomerID; UPDATE [Sales].[Customer] SET YTDOrders = 0, YTDSales = 0 WHERE [CustomerID] = @CustomerID; -- place 3 orders for that customer 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 -- database unit test for Sales.uspFillOrder 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'; -- 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;
這段程式碼會結合測試前的 Transact-SQL 程式碼與測試本身的 Transact-SQL。 您需要這兩個程式碼,才能在執行測試時傳回相同的結果。
按一下 [擷取]。
如此就會執行您所指定的 Transact-SQL,而且針對傳回的資料計算總和檢查碼。
按一下 [確定]。
計算的總和檢查碼就會與測試條件一起儲存。 需要的總和檢查碼會出現在 [資料總和檢查碼] 測試條件的 [值] 資料行中。
在 [檔案] 功能表上按一下 [全部儲存]。
此時,您已經準備好執行測試。
執行資料庫單元測試
若要執行資料庫單元測試
指向 [測試] 功能表上的 [視窗],然後按一下 [測試檢視]。
在 [測試檢視] 視窗中,按一下工具列上的 [重新整理] 更新測試清單。
[測試檢視] 視窗會列出您之前在這個逐步解說中建立的測試,以及您加入 Transact-SQL 陳述式和測試條件的測試。 名為 TestMethod1 的測試是空的,而且不會用於這個逐步解說。
以滑鼠右鍵按一下 [Sales_uspNewCustomerTest],然後按一下 [執行選取範圍]。
Visual Studio會使用您所指定的權限內容連接到資料庫,並套用資料產生計劃。 然後,Visual Studio 會在執行測試中的 Transact-SQL 指令碼之前,切換成執行內容。 最後,Visual Studio 會根據您在測試條件中指定的內容評估 Transact-SQL 指令碼的結果,而且成功或失敗的結果會顯示在 [測試結果] 視窗中。
檢視 [測試結果] 視窗中的結果。
測試成功,表示 SELECT 陳述式會在執行時傳回一個資料列。
針對 Sales_uspPlaceNewOrderTest、Sales_uspFillOrderTest 和 Sales_uspShowOrderDetailsTest 測試重複步驟 3。 結果應該如下所示:
測試
預期的結果
Sales_uspPlaceNewOrderTest
成功
Sales_uspShowOrderDetailsTest
成功
Sales_uspFillOrderTest
測試失敗,而且顯示下列錯誤:「ScalarValueCondition 條件 (scalarValueCondition2) 失敗: ResultSet 1 資料列 1 資料行 1: 值不相符,實際為 '-100',預期為 '100'」。發生這個錯誤的原因是預存程序的定義包含次要錯誤。
接下來,您將更正錯誤並重新執行測試。
若要更正 Sales.uspFillOrder 中的錯誤
在 [結構描述檢視] 中,按兩下 [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.proc.sql]。
在 [測試檢視] 中,以滑鼠右鍵按一下 [Sales_uspFillOrderTest],然後按一下 [執行選取範圍]。
測試就會成功。
加入負面單元測試
您可能會建立負面測試來驗證測試是否會在應該失敗時失敗。 例如,如果您嘗試取消已經填寫的訂單,該測試應該會失敗。 在這個部分的逐步解說中,您會針對 Sales.uspCancelOrder 預存程序建立負面單元測試。
若要建立並驗證負面測試,您必須執行下列工作:
更新要測試失敗條件的預存程序
定義新的單元測試
修改單元測試的程式碼,以便指出預期會失敗
執行單元測試
若要更新預存程序
在 [結構描述檢視] 中,依序展開 [SimpleUnitTestDB] 節點、[結構描述] 節點、[Sales] 節點、[可程式性] 節點和 [預存程序] 節點,然後按兩下 [uspCancelOrder]。
在 Transact-SQL 編輯器中,將程序定義更新為符合下列程式碼:
CREATE PROCEDURE [Sales].[uspCancelOrder] @OrderID INT AS BEGIN DECLARE @Delta INT, @CustomerID INT, @PriorStatus 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 NVARCHAR(4000); DECLARE @ErrorSeverity INT; DECLARE @ErrorState INT; SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); ROLLBACK TRANSACTION -- Use RAISERROR inside the CATCH block to return -- error information about the original error that -- caused execution to jump to the CATCH block. RAISERROR (@ErrorMessage, -- Mesasge text @ErrorSeverity, -- Severity @ErrorState -- State ); RETURN 0; -- indicate failure END CATCH; END
按一下 [檔案] 功能表上的 [儲存 uspCancelOrder.proc.sql]。
在 [方案總管] 中,以滑鼠右鍵按一下 [SimpleUnitTestDB],然後按一下 [部署]。
您就會將更新部署至 uspCancelOrder 預存程序。 由於您沒有變更其他物件,所以只會更新該預存程序。
接下來,您會定義與這個程序相關聯的單元測試。
若要為 uspCancelOrder 撰寫資料庫單元測試
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspCancelOrderTest],並確定旁邊的清單中反白顯示了 [測試]。
執行這個步驟之後,您就可以針對單元測試中的測試動作建立測試指令碼。
在 Transact-SQL 編輯器中更新 Transact-SQL 陳述式,以便符合下列陳述式:
-- database 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;
在 [測試條件] 窗格中,按一下結果不明的測試條件,然後按一下 [刪除測試條件] (x)。
在 [測試條件] 窗格的清單中,按一下 [純量值],然後按一下 [加入測試條件] (+)。
在 [屬性] 視窗中,將 [需要的值] 屬性設為 0。
在 [資料庫單元測試設計工具] 的巡覽列中,按一下 [Sales_uspCancelOrderTest],並確定旁邊的清單中反白顯示了 [測試前]。 執行這個步驟之後,您就可以指定陳述式,以便讓資料進入執行測試所需的狀態。 在這個範例中,您必須先建立客戶記錄,然後才能下訂單。
按一下 [按一下此處以建立] 建立測試前指令碼。
在 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, @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 from [Sales].[Orders] WHERE [CustomerID] = @CustomerID; UPDATE [Sales].[Customer] SET YTDOrders = 0, YTDSales = 0 WHERE [CustomerID] = @CustomerID; -- place an order for that customer EXECUTE @OrderID = [Sales].[uspPlaceNewOrder] @CustomerID, @Amount, @OrderDate, @Status; -- fill the order for that customer EXECUTE @RC = [Sales].[uspFillOrder] @OrderID, @FilledDate; COMMIT TRANSACTION
在 [檔案] 功能表上按一下 [全部儲存]。
此時,您已經準備好執行測試。
若要執行資料庫單元測試
在 [測試檢視] 中,以滑鼠右鍵按一下 [Sales_uspCancelOrderTest],然後按一下 [執行選取範圍]。
檢視 [測試結果] 視窗中的結果。
測試就會失敗,而且顯示下列錯誤:
Test method TestProject1.DatabaseUnitTests1.Sales_uspCancelOrderTest threw exception: System.Data.SqlClient.SqlException: You can only cancel open orders.
接下來,您會修改程式碼,以便指出預期會發生例外狀況。
若要修改單元測試的程式碼
在 [方案總管] 中,展開 [TestProject1]、以滑鼠右鍵按一下 [DatabaseUnitTests1.cs],然後按一下 [檢視程式碼]。
在程式碼編輯器中,巡覽至 Sales_uspCancelOrderTest 方法。 修改此方法的屬性,以便符合下列程式碼:
[TestMethod(), ExpectedSqlException(Severity=16, MatchFirstError=false, State=1)] public void Sales_uspCancelOrderTest()
您會指定預期將看見特定的 SQL 例外狀況。 您可以選擇性地指定特定錯誤代碼。 如果您沒有加入這個屬性,單元測試將會失敗,而且 [測試結果] 視窗會顯示一則訊息。
在 [檔案] 功能表上,按一下 [儲存 DatabaseUnitTests1.cs]。
接下來,您會重新執行單元測試,以便驗證它是否如預期般失敗。
若要重新執行資料庫單元測試
在 [測試檢視] 中,以滑鼠右鍵按一下 [Sales_uspCancelOrderTest],然後按一下 [執行選取範圍]。
檢視 [測試結果] 視窗中的結果。
測試成功,表示此程序會在應該失敗時失敗。
後續步驟
在典型的專案中,您會定義其他單元測試,以便驗證所有重要的資料庫物件是否都正確運作。 在完成這些測試集合後,您會將它們簽入「版本控制」(Version Control),與小組成員共用。
在建立基準後,您可以建立並修改資料庫物件,然後再建立相關聯的測試,以便驗證變更是否會破壞預期的行為。