この記事では、ユーティリティプロシージャを含むストアドプロシージャをテストするデータベースユニットのウォークスルーを提供します。
この記事では、メインのストアドプロシージャがユーティリティプロシージャに依存し、要件が満たされていることを確認するためにメインプロシージャを単体テストする必要がある場合の、データベースの単体テストのシナリオについて説明します。重要なのは、ユニットテストをコードの単一ユニットに対してのみ記述できるようにすることです。つまり、メインプロシージャ用に1つのユニットテストが必要であり、ユーティリティプロシージャ用に別のユニットテストが必要です。
単一のストアドプロシージャの単体テストは、コード内でユーティリティプロシージャを呼び出すプロシージャの単体テストに比べて簡単です。
ユーティリティプロシージャのシナリオと、それが通常のストアドプロシージャの単体テストと異なる理由を理解することは非常に重要です。
シナリオ:メイン手順内のユーティリティ手順
ユーティリティ手順のシナリオを理解するために、ユーティリティ手順の定義と例から始めましょう。
ユーティリティ手順とは
ユーティリティプロシージャは通常、メインプロシージャが特定のタスクを実行するために使用する小さなプロシージャです。たとえば、メインプロシージャ用に何かを取得したり、メインプロシージャに何かを追加したりします。
ユーティリティプロシージャのもう1つの定義は、メンテナンス目的で作成された小さなストアドプロシージャです。これには、任意の数のプロシージャによって、または直接呼び出されるシステムテーブルまたはビューが含まれる場合があります。
ユーティリティ手順の例
顧客が特定の製品を注文する顧客注文製品シナリオを考えてみてください。特定の顧客からのすべての注文を取得するためのメインプロシージャを作成すると、ユーティリティプロシージャを使用して、各注文が平日か週末かを理解するのに役立ちます。
このようにして、小さなユーティリティ手順を記述して、顧客が製品を注文した日付に基づいて「平日」または「週末」を返すことができます。
もう1つの例は、マスターデータベースの「sp_server_info」などのシステムストアドプロシージャで、SQLServerにインストールされているバージョン情報を提供します。
EXEC sys.sp_server_info
ユニットテストユーティリティの手順が異なる理由
前に説明したように、メインプロシージャ内で呼び出されるユーティリティプロシージャの単体テストは、単純なストアドプロシージャの単体テストよりも少し複雑です。
上記の顧客注文製品の例を考慮すると、ユーティリティプロシージャが正常に機能していることを確認するために単体テストを作成する必要があります。また、ユーティリティプロシージャを呼び出すメインプロシージャも正常に機能していることを確認するために単体テストを作成する必要があります。ビジネス要件。
これは次のように示されています:
ユーティリティ/メインプロシージャチャレンジからの分離
ユーティリティ手順を含む手順の単体テストを作成する際の主な課題は、メイン手順の単体テストを作成するときにユーティリティ手順の機能について心配しないようにすることです。ユーティリティ手順についても同じことが言えます。 。これは、このようなシナリオの単体テストを作成する際に留意する必要のある難しい作業です。
テスト対象の手順によっては、ユーティリティまたはメイン手順からの分離が必須です。単体テスト中に分離する場合は、次の点に注意する必要があります。
- メイン手順をユニットテストするときにユーティリティ手順から分離します。
- ユーティリティ手順の単体テスト時にメイン手順から分離します。
この記事は、メインプロシージャをユーティリティプロシージャから分離することにより、メインプロシージャの単体テストに焦点を当てていることを忘れないでください。
メイン手順とそのユーティリティ手順の作成
ユーティリティプロシージャがメインプロシージャによって使用されているシナリオの単体テストを作成するには、最初に次の前提条件が必要です。
- サンプルデータベース
- ビジネス要件
サンプルデータベースのセットアップ(SQLBookShop)
以下に示すように注文されたすべての本のレコードを含む、「SQLBookShop」と呼ばれる単純な2つのテーブルのサンプルデータベースを作成しています。
次のようにSQLBookShopサンプルデータベースを作成します。
-- (1) Create SQLBookShop database CREATE DATABASE SQLBookShop; GO
次のようにデータベースオブジェクト(テーブル)を作成してデータを入力します。
USE SQLBookShop; -- (2) Drop book and book order tables if they already exist IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME='BookOrder') DROP TABLE dbo.BookOrder IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME='Book') DROP TABLE dbo.Book IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_TYPE='View' AND t.TABLE_NAME='OrderedBooks') DROP VIEW dbo.OrderedBooks -- (3) Create book table CREATE TABLE Book (BookId INT PRIMARY KEY IDENTITY(1,1), Title VARCHAR(50), Stock INT, Price DECIMAL(10,2), Notes VARCHAR(200) ) -- (4) Create book order table CREATE TABLE dbo.BookOrder (OrderId INT PRIMARY KEY IDENTITY(1,1), OrderDate DATETIME2, BookId INT, Quantity INT, TotalPrice DECIMAL(10,2) ) -- (5) Adding foreign keys for author and article category ALTER TABLE dbo.BookOrder ADD CONSTRAINT FK_Book_BookId FOREIGN KEY (BookId) REFERENCES Book (BookId) -- (6) Populaing book table INSERT INTO dbo.Book (Title, Stock, Price, Notes) VALUES ('Mastering T-SQL in 30 Days', 10, 200, ''), ('SQL Database Reporting Fundamentals', 5, 100, ''), ('Common SQL Mistakes by Developers',15,100,''), ('Optimising SQL Queries',20,200,''), ('Database Development and Testing Tips',30,50,''), ('Test-Driven Database Development (TDDD)',20,200,'') -- (7) Populating book order table INSERT INTO dbo.BookOrder (OrderDate, BookId, Quantity, TotalPrice) VALUES ('2018-01-01', 1, 2, 400), ('2018-01-02', 2, 2, 200), ('2018-01-03', 3, 2, 200), ('2018-02-04', 1, 2, 400), ('2018-02-05', 1, 3, 600), ('2018-02-06', 4, 3, 600), ('2018-03-07', 5, 2, 100), ('2018-03-08', 6, 2, 400), ('2018-04-10', 5, 2, 100), ('2018-04-11', 6, 3, 600); GO -- (8) Creating database view to see all the books ordered by customers CREATE VIEW dbo.OrderedBooks AS SELECT bo.OrderId ,bo.OrderDate ,b.Title ,bo.Quantity ,bo.TotalPrice FROM BookOrder bo INNER JOIN Book b ON bo.BookId = b.BookId
クイックチェック–サンプルデータベース
次のコードを使用してOrderedBooksビューを実行し、データベースをすばやくチェックします。
USE SQLBookShop -- Run OrderedBooks view SELECT ob.OrderID ,ob.OrderDate ,ob.Title AS BookTitle ,ob.Quantity ,ob.TotalPrice FROM dbo.OrderedBooks ob
SQLServer用のdbForgeStudioを使用しているため、SSMS(SQL Server Management Studio)で同じコードを実行すると、出力の外観が異なる場合があることに注意してください。ただし、スクリプトとその結果に違いはありません。
追加情報を含む最新の注文を確認するためのビジネス要件
「エンドユーザーは、特定の書籍の最新の注文と、その注文が平日か週末かを知りたい」というビジネス要件が開発チームに送信されました。
TDDDについての一言
この記事では、テスト駆動データベース開発(TDDD)に厳密に従っていませんが、テスト駆動データベース開発(TDDD)を使用して、単体テストを作成してオブジェクトが存在するかどうかを確認することから始まるメイン手順とユーティリティ手順の両方を作成することを強くお勧めします。最初は失敗し、次にオブジェクトを作成して、合格する必要のある単体テストを再実行します。
詳細なウォークスルーについては、この記事の最初の部分を参照してください。
ユーティリティ手順の特定
ビジネス要件を確認することの1つは、特定の日付が平日か週末かを判断できるユーティリティ手順が必要であることです。
ユーティリティプロシージャ(GetDayType)の作成
ユーティリティプロシージャを作成し、次のように「GetDayType」と呼びます。
-- Creating utility procedure to check whether the date passed to it is a weekday or weekend CREATE PROCEDURE dbo.uspGetDayType @OrderDate DATETIME2,@DayType CHAR(7) OUT AS BEGIN SET NOCOUNT ON IF (SELECT DATENAME(WEEKDAY, @OrderDate)) = 'Saturday' OR (SELECT DATENAME(WEEKDAY, @OrderDate)) = 'Sunday' SELECT @DayType= 'Weekend' ELSE SELECT @DayType = 'Weekday' SET NOCOUNT OFF END GO
クイックチェック–ユーティリティ手順
ユーティリティ手順をすばやく確認するには、次のコード行を記述します。
-- Quick check utility procedure declare @DayType varchar(10) EXEC uspGetDayType '20181001',@DayType output select @DayType AS [Type of Day]
メインプロシージャの作成(GetLatestOrderByBookId)
メインプロシージャを作成して、特定の本の最新の注文と、注文が平日か週末かを確認し、次のようにユーティリティプロシージャの呼び出しを含む「GetLatestOrderByBookId」と呼びます。
-- Creating stored procedure to get most recent order based on bookid and also whether order was placed on weekend or weekday CREATE PROCEDURE dbo.uspGetLatestOrderByBookId @BookId INT AS BEGIN -- Declare variables to store values DECLARE @OrderId INT ,@Book VARCHAR(50) ,@OrderDate DATETIME2 ,@Quantity INT ,@TotalPrice DECIMAL(10, 2) ,@DayType VARCHAR(10) -- Get most recent order for a particular book and initialise variables SELECT TOP 1 @OrderId = bo.OrderId ,@Book = b.Title ,@OrderDate = bo.OrderDate ,@Quantity = bo.Quantity ,@TotalPrice = bo.TotalPrice FROM BookOrder bo INNER JOIN Book b ON bo.BookId = b.BookId WHERE bo.BookId = @BookId ORDER BY OrderDate DESC -- Call utility procedure to get type of day for the above selected most recent order EXEC uspGetDayType @OrderDate ,@DayType OUTPUT -- Show most recent order for a particular book along with the information whether order was placed on weekday or weekend SELECT @OrderId AS OrderId ,@OrderDate AS OrderDate ,@Book AS Book ,@Quantity AS Quantity ,@TotalPrice AS TotalPrice ,@DayType AS DayType END GO
クイックチェック–主な手順
次のコードを実行して、手順が正常に機能しているかどうかを確認します。
-- Get latest order for the bookid=6 EXEC uspGetLatestOrderByBookId @BookId = 6
ユーティリティプロシージャを呼び出すユニットテストのメインプロシージャ
ここで重要なのは、メイン手順とユーティリティ手順の単体テストの違いを理解することです。
現在、メインプロシージャの単体テストに重点を置いているため、ユーティリティプロシージャをこの単体テストから適切に分離する必要があります。
スパイ手順の使用
メインプロシージャの単体テストがメインプロシージャの機能のテストに集中していることを確認するには、ユーティリティプロシージャのスタブ(プレースホルダー)として機能するtSQLtが提供するスパイプロシージャを使用する必要があります。
tsqlt.orgによると、プロシージャをスパイしている場合は、実際にそのプロシージャを単体テストしているのではなく、スパイしているプロシージャに関連する他のプロシージャを単体テストしやすくしていることを覚えておいてください。
たとえば、この場合、メインプロシージャの単体テストを行う場合は、スパイプロシージャを使用してユーティリティプロシージャをモックする必要があります。これにより、メインプロシージャの単体テストが容易になります。
メイン手順スパイユーティリティ手順の単体テストの作成
データベース単体テストを作成して、メインプロシージャの機能を正しくチェックします。
この記事は、 dbForge Studio for SQL Server(またはdbForgeユニットテストのみ)で機能します。 およびSSMS(SQL Server Management Studio) 。ただし、SSMS(SQL Server Management Studio)を使用する場合は、tSQLt Frameworkが既にインストールされており、単体テストを作成する準備ができていることを前提としています。
最初のデータベース単体テストを作成するには、SQLBookShopデータベースを右クリックします。ショートカットメニューで、次のように[単体テスト]、[新しいテストの追加]の順にクリックします。
ユニットテストコードを書く:
CREATE PROCEDURE GetLatestOrder.[test to check uspGetLatestOrderByBookId outputs correct data] AS BEGIN --Assemble -- Mock order Book and BookOrder table EXEC tSQLt.FakeTable @TableName='dbo.Book' EXEC tSQLt.FakeTable @TableName='dbo.BookOrder' -- Adding mock data to book table INSERT INTO dbo.Book (BookId,Title, Stock, Price, Notes) VALUES (1,'Basics of T-SQL Programming', 10, 100, ''), (2,'Advanced T-SQL Programming', 10, 200, '') -- Adding mock data to bookorder table INSERT INTO dbo.BookOrder (OrderId,OrderDate, BookId, Quantity, TotalPrice) VALUES (1,'2018-01-01', 1, 2, 200), (2,'2018-05-01', 1, 2, 200), (3,'2018-07-01', 2, 2, 400) -- Creating expected table CREATE TABLE GetLatestOrder.Expected ( OrderId INT ,OrderDate DATETIME2 ,Book VARCHAR(50) ,Quantity INT ,TotalPrice DECIMAL(10, 2) ,DayType VARCHAR(10) ) -- Creating actual table CREATE TABLE GetLatestOrder.Actual ( OrderId INT ,OrderDate DATETIME2 ,Book VARCHAR(50) ,Quantity INT ,TotalPrice DECIMAL(10, 2) ,DayType VARCHAR(10) ) -- Creating uspGetDayType spy procedure to isolate main procedure from it so that main procedure can be unit tested EXEC tSQLt.SpyProcedure @ProcedureName = 'dbo.uspGetDayType',@CommandToExecute = 'set @DayType = ''Weekday'' ' -- Inserting expected values to the expected table INSERT INTO GetLatestOrder.Expected (OrderId, OrderDate, Book, Quantity, TotalPrice, DayType) VALUES (2,'2018-05-01', 'Basics of T-SQL Programming', 2, 200,'Weekday'); --Act INSERT INTO GetLatestOrder.Actual EXEC uspGetLatestOrderByBookId @BookId = 1 -- Calling the main procedure --Assert --Compare expected results with actual table results EXEC tSQLt.AssertEqualsTable @Expected = N'GetLatestOrder.Expected', -- nvarchar(max) @Actual = N'GetLatestOrder.Actual' -- nvarchar(max) END; GO
メインプロシージャの単体テストの実行
単体テストを実行します:
おめでとうございます。スパイプロシージャを使用した後、ストアドプロシージャをユーティリティプロシージャから分離することで、ストアドプロシージャの単体テストに成功しました。
単体テストの詳細については、テスト駆動データベース開発(TDDD)に関する前回の記事の次の部分を参照してください。
- ジャンプしてテスト駆動データベース開発(TDDD)を開始–パート1
- ジャンプしてテスト駆動データベース開発(TDDD)を開始–パート2
- ジャンプしてテスト駆動データベース開発(TDDD)を開始–パート3
やるべきこと
ストアドプロシージャがユーティリティプロシージャを呼び出す、少し複雑なシナリオのデータベース単体テストを作成できるようになりました。
- スパイプロシージャ@CommandToExecute引数(値)を@CommandToExecute =‘set @DayType =” Nothing”‘に変更してみて、テストが失敗することを確認してください
- テスト駆動データベース開発(TDDD)を使用して、この記事のビジネス要件を満たしてみてください
- 同じユーティリティ手順を含むテスト駆動開発(TDDD)を使用して顧客が行った最新の注文を確認するには、別のビジネス要件を満たすようにしてください。
- メインプロシージャを分離して、ユーティリティプロシージャの単体テストを作成してみてください
- 2つのユーティリティプロシージャを呼び出すプロシージャの単体テストを作成してみてください
便利なツール:
dbForge単体テスト– SQL ServerManagementStudioで自動単体テストを実装するための直感的で便利なGUI。