sql >> Databasteknik >  >> RDS >> Database

Förenkla enhetstestning Huvudlagrad procedur som också kallar en hjälpprocedur

Den här artikeln ger en genomgång av en databasenhet som testar en lagrad procedur som innehåller en verktygsprocedur i den.

I den här artikeln kommer jag att diskutera ett scenario för databasenhetstestning när en huvudsaklig lagrad procedur beror på en verktygsprocedur och huvudproceduren måste enhetstestades för att säkerställa att kraven uppfylls. Nyckeln är att säkerställa att ett enhetstest endast kan skrivas för en enda kodenhet, vilket innebär att vi behöver ett enhetstest för huvudproceduren och ett annat enhetstest för verktygsproceduren.

Enhetstestning av en enskild lagrad procedur är enklare jämfört med att enhetstesta en procedur som anropar en verktygsprocedur i sin kod.

Det är mycket viktigt att förstå scenariot med verktygsproceduren och varför det är annorlunda än att enhetstesta en normal lagrad procedur.

Scenario:Verktygsprocedur inom huvudprocedur

För att förstå scenariot med verktygsprocedur låt oss börja med definitionen och exemplet på verktygsprocedur:

Vad är Utility Procedur

En hjälpprocedur är i allmänhet en liten procedur som används av huvudprocedurerna för att utföra någon specifik uppgift som att få något för huvudproceduren eller lägga till något till huvudproceduren.

En annan definition av verktygsprocedur är en liten lagrad procedur skriven för underhållsändamål som kan involvera systemtabeller eller vyer som ska anropas av ett valfritt antal procedurer eller till och med direkt.

Exempel på verktygsprocedur

Tänk på ett kund-order-produkt scenario där en kund gör en beställning för en viss produkt. Om vi ​​skapar huvudproceduren för att få oss alla beställningar som lagts av en viss kund, kan en hjälpprocedur användas för att hjälpa oss att förstå om varje beställning gjordes av kunden på vardagar eller helger.
På detta sätt kan en liten verktygsprocedur kan skrivas för att returnera "Weekday" eller "Weekend" baserat på det datum produkten beställdes av kunden.

Ett annat exempel kan vara systemlagrade procedurer som "sp_server_info" i huvuddatabasen som ger SQL Server installerad versionsinformation:

EXEC sys.sp_server_info

Varför förfarandet för enhetstestning är annorlunda

Som diskuterats tidigare är enhetstestning av en verktygsprocedur som anropas i huvudproceduren något komplicerad än enhetstestning av en enkel lagrad procedur.

Med tanke på kund-order-produktexemplet som nämnts ovan, måste vi skriva ett enhetstest för att kontrollera att verktygsproceduren fungerar bra och även ett enhetstest måste skrivas för att kontrollera att huvudproceduren som anropar verktygsproceduren också fungerar korrekt samt möter affärskraven.

Detta illustreras enligt följande:

Isolera från Utility/Main Procedure Challenge

Den största utmaningen med att skriva ett enhetstest(er) för proceduren som involverar ett verktygsförfarande är att se till att vi inte ska oroa oss för hur verktygsförfarandet fungerar när vi skriver ett enhetstest för huvudproceduren och detsamma gäller för verktygsförfarandet. . Detta är en utmanande uppgift som måste hållas i åtanke när du skriver enhetstester för ett sådant scenario.
Isolering från verktyget eller huvudproceduren är ett måste, beroende på vilken procedur som testas. Vi måste ha följande saker i åtanke i samband med isolering under enhetstestning:

  1. Isolera från verktygsproceduren när huvudproceduren testas av enheten.
  2. Isolering från huvudproceduren vid enhetstestning av verktygsprocedur.

Kom ihåg att den här artikeln fokuserar på enhetstestning av huvudproceduren genom att isolera den från dess verktygsprocedur.

Skapa huvudprocedur och dess verktygsprocedur

För att kunna skriva ett enhetstest för ett scenario där verktygsproceduren används av huvudproceduren måste vi först ha följande förutsättningar:

  1. Exempeldatabas
  2. Företagskrav

Setup Sample Database (SQLBookShop)

Vi skapar en enkel exempeldatabas med två tabeller som heter "SQLBookShop" som innehåller register över alla böcker som beställts enligt nedan:

Skapa SQLBookShop exempeldatabas enligt följande:

-- (1) Create SQLBookShop database
  CREATE DATABASE SQLBookShop;
  GO

Skapa och fyll i databasobjekt (tabeller) enligt följande:

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

Snabbkontroll – exempeldatabas

Gör en snabb databaskontroll genom att köra vyn OrderedBooks med följande kod:

USE SQLBookShop

-- Run OrderedBooks view
SELECT
  ob.OrderID
 ,ob.OrderDate
 ,ob.Title AS BookTitle
 ,ob.Quantity
 ,ob.TotalPrice
FROM dbo.OrderedBooks ob

Observera att jag använder dbForge Studio för SQL Server så utdatautseendet kan skilja sig om du kör samma kod i SSMS (SQL Server Management Studio). Det finns dock ingen skillnad mellan skript och deras resultat.

Företagskrav för att se den senaste beställningen med ytterligare information

Ett affärskrav har skickats till utvecklarteamet som säger att "Slutanvändaren vill veta om den senaste beställningen för en viss bok tillsammans med informationen om beställningen gjordes på en vardag eller helg"

Ett ord om TDDD

Vi följer inte strikt testdriven databasutveckling (TDDD) i den här artikeln men jag rekommenderar starkt att använda testdriven databasutveckling (TDDD) för att skapa både huvud- och verktygsprocedurer som börjar med att skapa ett enhetstest för att kontrollera om objektet existerar som misslyckas först, följt av att skapa objektet och köra enhetstestet igen som måste godkännas.
För en detaljerad genomgång, se den första delen av den här artikeln.

Procedur för att identifiera verktyg

När vi ser affärskravet är en sak säker att vi behöver ett verktygsprocedur som kan berätta för oss om ett visst datum är en vardag eller en helg.

Skapa verktygsprocedur (GetDayType)

Skapa en verktygsprocedur och kalla den "GetDayType" enligt följande:

-- 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

Snabbkontroll – Utility Procedur

Skriv följande kodrader för att snabbt kontrollera verktygsproceduren:

-- Quick check utility procedure
declare @DayType varchar(10)
EXEC uspGetDayType '20181001',@DayType output
select @DayType AS [Type of Day]

Skapa huvudprocedur (GetLatestOrderByBookId)

Skapa huvudproceduren för att se den senast gjorda beställningen för en viss bok och även om beställningen gjordes på en veckodag eller helg och kalla den "GetLatestOrderByBookId" som innehåller anropet för verktygsproceduren enligt följande:

-- 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

Snabbkontroll – Huvudprocedur

Kör följande kod för att se om proceduren fungerar bra eller inte:

-- Get latest order for the bookid=6
EXEC uspGetLatestOrderByBookId @BookId = 6

Huvudprocedur för enhetstestning Anropsverktygsprocedur

Nyckeln här är att förstå skillnaden mellan enhetstestning av huvudproceduren och verktygsproceduren.

Vi är för närvarande fokuserade på att enhetstesta huvudproceduren, så det betyder att verktygsproceduren måste isoleras på ett elegant sätt från detta enhetsteste.

Användning av spionprocedur

För att säkerställa att huvudprocedurens enhetstest förblir fokuserad på att testa funktionaliteten hos huvudproceduren, måste vi använda spionproceduren tillhandahållen av tSQLt som kommer att fungera som en stubb (platshållare) för verktygsproceduren.

Enligt tsqlt.org, kom ihåg att om du spionerar på en procedur så är du faktiskt inte enhetstestad den proceduren, snarare gör du det lättare för den andra proceduren som är relaterad till proceduren du spionerar att enhetstestas.

Till exempel, i vårt fall, om vi vill enhetstesta huvudproceduren måste vi håna verktygsproceduren genom att använda spionproceduren som kommer att göra det lättare för oss att enhetstesta huvudproceduren.

Skapa enhetstest för huvudprocedur för spionprogram

Skapa ett databasenhetstest för att kontrollera att huvudproceduren fungerar korrekt.

Den här artikeln fungerar för dbForge Studio för SQL Server (eller endast dbForge Unit Test) och SSMS (SQL Server Management Studio) . Observera dock att när du använder SSMS (SQL Server Management Studio) antar jag att du redan har installerat tSQLt Framework och redo att skriva enhetstesten.

För att skapa det första databasenhetstestet, högerklicka på SQLBookShop-databasen. På genvägsmenyn klickar du på Enhetstest och sedan på Lägg till nytt test enligt följande:

Skriv enhetens testkod:

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

Kör enhetstest för huvudprocedur

Kör enhetstestet:

Grattis, du har framgångsrikt enhetstestat en lagrad procedur genom att isolera den från dess verktygsprocedur efter att ha använt spionprocedur.

För mer information om enhetstestning, gå igenom följande delar av min tidigare artikel om testdriven databasutveckling (TDDD):

  • Hoppa till Start Testdriven Database Development (TDDD) – Del 1
  • Hoppa till Start Testdriven Database Development (TDDD) – Del 2
  • Hoppa till Start Testdriven Database Development (TDDD) – Del 3

Saker att göra

Du kan nu skapa databasenhetstester för lite komplexa scenarier där lagrade procedurer anropar verktygsprocedurer.

  1. Försök att ändra spionproceduren @CommandToExecute argument (värde) som @CommandToExecute =‘set @DayType =”Ingenting” ‘ och se att testet kommer att misslyckas nu
  2. Försök att uppfylla affärskraven i den här artikeln genom att använda testdriven databasutveckling (TDDD)
  3. Försök att uppfylla ett annat affärskrav för att se den senaste beställningen som lagts av en kund som använder testdriven utveckling (TDDD) som involverar samma verktygsprocedur
  4. Försök att skapa ett enhetstest för verktygsprocedur genom att isolera huvudproceduren
  5. Ge dig själv ett försök att skapa ett enhetstest för en procedur som anropar två verktygsprocedurer

Användbart verktyg:

dbForge Unit Test – ett intuitivt och bekvämt gränssnitt för att implementera automatiserad enhetstestning i SQL Server Management Studio.


  1. 2 funktioner som returnerar månadens namn från ett datum i MySQL

  2. Många-till-många relationer exempel

  3. snabbt slumpmässigt radval i Postgres

  4. SQLite uttrycksbaserat index