Database
 sql >> Database >  >> RDS >> Database

Semplificare la procedura memorizzata principale del test unitario che chiama anche una procedura di utilità

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:

  1. Isolamento dalla procedura di utilità durante il test unitario della procedura principale.
  2. 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:

  1. Banca dati campione
  2. 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à.

  1. Prova a modificare l'argomento della procedura spia @CommandToExecute (valore) come @CommandToExecute ='set @DayType =”Nothing” 'e vedrai che il test fallirà ora
  2. Prova a soddisfare i requisiti aziendali in questo articolo utilizzando lo sviluppo di database basato su test (TDDD)
  3. 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à
  4. Prova a creare uno unit test per la procedura di utilità isolando la procedura principale
  5. 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.