Questo articolo fornisce una procedura dettagliata del test di unità di database di una stored procedure che contiene una procedura di utilità al suo interno.
In questo articolo, parlerò di uno scenario di unit test del database quando una stored procedure principale dipende da una procedura di utilità e la procedura principale deve essere sottoposta a unit test per assicurarsi che i requisiti siano soddisfatti. La chiave è garantire che uno unit test possa essere scritto solo per una singola unità di codice, il che significa che abbiamo bisogno di uno unit test per la procedura principale e un altro unit test per la procedura di utilità.
Il test unitario di una singola stored procedure è più semplice rispetto allo unit test di una procedura che chiama una procedura di utilità all'interno del suo codice.
È molto importante comprendere lo scenario della procedura di utilità e perché è diverso dallo unit test di una normale procedura memorizzata.
Scenario:procedura di utilità all'interno della procedura principale
Per comprendere lo scenario della procedura di utilità, iniziamo con la definizione e l'esempio di procedura di utilità:
Cos'è la procedura di utilità
Una procedura di utilità è generalmente una piccola procedura che viene utilizzata dalle procedure principali per eseguire alcune attività specifiche come ottenere qualcosa per la procedura principale o aggiungere qualcosa alla procedura principale.
Un'altra definizione di procedura di utilità è una piccola procedura memorizzata scritta a scopo di manutenzione che può coinvolgere tabelle o viste di sistema da richiamare da un numero qualsiasi di procedure o anche direttamente.
Esempi di procedura di utilità
Pensa a uno scenario ordine cliente-prodotto in cui un cliente effettua un ordine per un particolare prodotto. Se creiamo la procedura principale per riceverci tutti gli ordini effettuati da un determinato cliente, allora una procedura di utilità può essere utilizzata per aiutarci a capire se ogni ordine è stato effettuato dal cliente nei giorni feriali o nel fine settimana.
In questo modo, un piccola procedura di utilità può essere scritta per restituire “Weekday” o “Weekend” in base alla data in cui il prodotto è stato ordinato dal cliente.
Un altro esempio possono essere le procedure memorizzate di sistema come "sp_server_info" nel database master che fornisce informazioni sulla versione installata di SQL Server:
EXEC sys.sp_server_info
Perché la procedura dell'utilità di test unitario è diversa
Come discusso in precedenza, il test di unità di una procedura di utilità che viene chiamata all'interno della procedura principale è leggermente complicato rispetto al test di unità di una semplice procedura memorizzata.
Considerando l'esempio di prodotto-ordine cliente menzionato sopra, è necessario scrivere uno unit test per verificare che la procedura di utilità funzioni correttamente e anche uno unit test deve essere scritto per verificare che la procedura principale che richiama la procedura di utilità funzioni correttamente oltre a soddisfare i requisiti aziendali.
Questo è illustrato come segue:
Isolamento dalla sfida di utilità/procedura principale
La sfida principale nello scrivere uno o più unit test per la procedura che implica una procedura di utilità è assicurarsi che non ci si debba preoccupare del funzionamento della procedura di utilità quando si scrive uno unit test per la procedura principale e lo stesso vale per la procedura di utilità . Questo è un compito impegnativo che deve essere tenuto a mente durante la scrittura di unit test per tale scenario.
L'isolamento dall'utilità o dalla procedura principale è un must, a seconda della procedura in fase di test. Dobbiamo tenere a mente le seguenti cose nel contesto dell'isolamento durante il test unitario:
- Isolamento dalla procedura di utilità durante il test unitario della procedura principale.
- Isolamento dalla procedura principale durante la procedura di utilità di unit test.
Ricorda che questo articolo è incentrato sul test unitario della procedura principale isolandola dalla sua procedura di utilità.
Creazione della procedura principale e relativa procedura di utilità
Per scrivere uno unit test per uno scenario in cui la procedura di utilità è utilizzata dalla procedura principale, dobbiamo prima avere i seguenti prerequisiti:
- Banca dati campione
- Requisiti aziendali
Impostazione del database di esempio (SQLBookShop)
Stiamo creando un semplice database di esempio a due tabelle chiamato "SQLBookShop" che contiene i record di tutti i libri ordinati come mostrato di seguito:
Crea un database di esempio SQLBookShop come segue:
-- (1) Create SQLBookShop database CREATE DATABASE SQLBookShop; GO
Crea e popola gli oggetti del database (tabelle) come segue:
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
Controllo rapido:database di esempio
Esegui un rapido controllo del database eseguendo la visualizzazione OrderedBooks utilizzando il codice seguente:
USE SQLBookShop -- Run OrderedBooks view SELECT ob.OrderID ,ob.OrderDate ,ob.Title AS BookTitle ,ob.Quantity ,ob.TotalPrice FROM dbo.OrderedBooks ob
Si noti che sto usando dbForge Studio per SQL Server, quindi l'aspetto dell'output potrebbe differire se si esegue lo stesso codice in SSMS (SQL Server Management Studio). Tuttavia, non vi è alcuna differenza tra gli script ei relativi risultati.
Requisito aziendale per visualizzare l'ultimo ordine con informazioni aggiuntive
È stato inviato un requisito aziendale al team di sviluppo in cui si afferma che "L'utente finale desidera conoscere l'ordine più recente effettuato per un determinato libro insieme alle informazioni se l'ordine è stato effettuato in un giorno feriale o nel fine settimana"
Una parola su TDDD
In questo articolo non seguiamo rigorosamente lo sviluppo di database basato su test (TDDD), ma consiglio vivamente di utilizzare lo sviluppo di database basato su test (TDDD) per creare procedure sia principali che di utilità che iniziano creando uno unit test per verificare se esiste un oggetto che inizialmente non riesce, quindi crea l'oggetto ed esegue nuovamente lo unit test che deve essere superato.
Per una procedura dettagliata, fare riferimento alla prima parte di questo articolo.
Procedura di identificazione dell'utilità
Considerando i requisiti aziendali, una cosa è sicuramente che abbiamo bisogno di una procedura di utilità che possa dirci se una data particolare è un giorno feriale o un fine settimana.
Creazione della procedura di utilità (GetDayType)
Crea una procedura di utilità e chiamala "GetDayType" come segue:
-- 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
Controllo rapido – Procedura di utilità
Scrivi le seguenti righe di codice per controllare rapidamente la procedura di utilità:
-- Quick check utility procedure declare @DayType varchar(10) EXEC uspGetDayType '20181001',@DayType output select @DayType AS [Type of Day]
Creazione della procedura principale (GetLatestOrderByBookId)
Crea la procedura principale per vedere l'ordine più recente effettuato per un determinato libro e anche se l'ordine è stato effettuato in un giorno feriale o nel fine settimana e chiamalo “GetLatestOrderByBookId” che contiene la chiamata per la procedura di utilità come segue:
-- 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
Controllo rapido – Procedura principale
Esegui il codice seguente per vedere se la procedura funziona correttamente o meno:
-- Get latest order for the bookid=6 EXEC uspGetLatestOrderByBookId @BookId = 6
Procedura principale di Unit Test Calling Utility Procedure
La chiave qui è capire la differenza tra il test unitario della procedura principale e la procedura di utilità.
Attualmente ci stiamo concentrando sul test unitario della procedura principale, quindi ciò significa che la procedura di utilità deve essere isolata con garbo da questo test unitario.
Utilizzo della procedura spia
Per assicurarci che lo unit test della procedura principale rimanga focalizzato sul test della funzionalità della procedura principale, dobbiamo usare la procedura spia fornita da tSQLt che fungerà da stub (segnaposto) per la procedura di utilità.
Secondo tsqlt.org, ricorda che se stai spiando una procedura non stai effettivamente testando l'unità di quella procedura, piuttosto stai rendendo più semplice il test dell'altra procedura relativa alla procedura che stai spiando.
Ad esempio, nel nostro caso, se vogliamo testare l'unità della procedura principale, dobbiamo prendere in giro la procedura di utilità utilizzando la procedura spy che ci renderà più facile testare l'unità della procedura principale.
Creazione di unit test per la procedura principale dell'utility di spionaggio
Crea uno unit test del database per verificare il corretto funzionamento della procedura principale.
Questo articolo funziona per dbForge Studio per SQL Server (o solo dbForge Unit Test) e SSMS (SQL Server Management Studio) . Tuttavia, tieni presente che quando usi SSMS (SQL Server Management Studio) presumo che tu abbia già installato tSQLt Framework e pronto per scrivere gli unit test.
Per creare il primo unit test del database, fare clic con il pulsante destro del mouse sul database di SQLBookShop. Nel menu di scelta rapida, fare clic su Unit Test e quindi su Aggiungi nuovo test come segue:
Scrivi il codice unit test:
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
Esecuzione del test unitario per la procedura principale
Esegui lo unit test:
Congratulazioni, hai testato con successo una procedura memorizzata isolandola dalla sua procedura di utilità dopo aver utilizzato la procedura spia.
Per ulteriori informazioni sui test unitari, consulta le seguenti parti del mio precedente articolo sullo sviluppo di database basato su test (TDDD):
- Vai all'inizio dello sviluppo di database basato su test (TDDD) – Parte 1
- Vai all'avvio dello sviluppo di database basato su test (TDDD) – Parte 2
- Vai all'avvio dello sviluppo di database basato su test (TDDD) – Parte 3
Cose da fare
È ora possibile creare unit test di database per scenari leggermente complessi in cui le stored procedure richiamano le procedure di utilità.
- Prova a modificare l'argomento della procedura spia @CommandToExecute (valore) come @CommandToExecute ='set @DayType =”Nothing” 'e vedrai che il test fallirà ora
- Prova a soddisfare i requisiti aziendali in questo articolo utilizzando lo sviluppo di database basato su test (TDDD)
- Cerca di soddisfare un altro requisito aziendale per vedere l'ordine più recente effettuato da qualsiasi cliente utilizzando lo sviluppo basato su test (TDDD) che prevede la stessa procedura di utilità
- Prova a creare uno unit test per la procedura di utilità isolando la procedura principale
- Per favore, prova a creare uno unit test per una procedura che chiama due procedure di utilità
Strumento utile:
dbForge Unit Test:una GUI intuitiva e conveniente per l'implementazione di unit test automatizzati in SQL Server Management Studio.