Database
 sql >> Base de données >  >> RDS >> Database

Simplification de la procédure stockée principale des tests unitaires qui appelle également une procédure utilitaire

Cet article fournit une présentation du test unitaire de base de données d'une procédure stockée contenant une procédure utilitaire.

Dans cet article, je vais discuter d'un scénario de test unitaire de base de données lorsqu'une procédure stockée principale dépend d'une procédure utilitaire et que la procédure principale doit être testée unitaire afin de s'assurer que les exigences sont remplies. La clé est de s'assurer qu'un test unitaire ne peut être écrit que pour une seule unité de code, ce qui signifie que nous avons besoin d'un test unitaire pour la procédure principale et d'un autre test unitaire pour la procédure utilitaire.

Le test unitaire d'une procédure stockée unique est plus simple que le test unitaire d'une procédure qui appelle une procédure utilitaire dans son code.

Il est très important de comprendre le scénario de la procédure utilitaire et pourquoi il est différent du test unitaire d'une procédure stockée normale.

Scénario :procédure utilitaire dans la procédure principale

Afin de comprendre le scénario de procédure utilitaire, commençons par la définition et l'exemple de procédure utilitaire :

Qu'est-ce que la procédure utilitaire

Une procédure utilitaire est généralement une petite procédure qui est utilisée par la ou les procédures principales pour effectuer une tâche spécifique, comme obtenir quelque chose pour la procédure principale ou ajouter quelque chose à la procédure principale.

Une autre définition de procédure utilitaire est une petite procédure stockée écrite à des fins de maintenance qui peut impliquer des tables système ou des vues à appeler par n'importe quel nombre de procédures ou même directement.

Exemples de procédure utilitaire

Pensez à un scénario client-commande-produit où un client passe une commande pour un produit particulier. Si nous créons la procédure principale pour obtenir toutes les commandes passées par un client particulier, une procédure utilitaire peut être utilisée pour nous aider à comprendre si chaque commande a été passée par le client en semaine ou le week-end.
De cette façon, une Une petite procédure utilitaire peut être écrite pour renvoyer « Jour de la semaine » ou « Week-end » en fonction de la date à laquelle le produit a été commandé par le client.

Un autre exemple peut être les procédures stockées système telles que "sp_server_info" dans la base de données principale qui donne des informations sur la version installée de SQL Server :

EXEC sys.sp_server_info

Pourquoi la procédure de l'utilitaire de test unitaire est différente

Comme indiqué précédemment, le test unitaire d'une procédure utilitaire qui est appelée dans la procédure principale est un peu plus compliqué que le test unitaire d'une simple procédure stockée.

Considérant l'exemple client-commande-produit mentionné ci-dessus, nous devons écrire un test unitaire pour vérifier que la procédure utilitaire fonctionne correctement et également un test unitaire doit être écrit pour vérifier que la procédure principale qui appelle la procédure utilitaire fonctionne également correctement ainsi que la réunion le(s) besoin(s) métier.

Ceci est illustré comme suit :

Isolation de l'utilitaire/défi de procédure principale

Le principal défi dans l'écriture d'un ou plusieurs tests unitaires pour la procédure qui implique une procédure utilitaire est de s'assurer que nous ne devons pas nous soucier du fonctionnement de la procédure utilitaire lors de l'écriture d'un test unitaire pour la procédure principale et il en va de même pour la procédure utilitaire. . Il s'agit d'une tâche difficile qui doit être gardée à l'esprit lors de l'écriture de tests unitaires pour un tel scénario.
Il est indispensable de s'isoler de l'utilitaire ou de la procédure principale, en fonction de la procédure testée. Nous devons garder à l'esprit les éléments suivants dans le contexte de l'isolement lors des tests unitaires :

  1. Isolation de la procédure utilitaire lors du test unitaire de la procédure principale.
  2. Isolation de la procédure principale lors de la procédure de l'utilitaire de test unitaire.

N'oubliez pas que cet article se concentre sur les tests unitaires de la procédure principale en l'isolant de sa procédure utilitaire.

Création de la procédure principale et de sa procédure utilitaire

Afin d'écrire un test unitaire pour un scénario où la procédure utilitaire est utilisée par la procédure principale, nous devons d'abord avoir les prérequis suivants :

  1. Exemple de base de données
  2. Exigence(s) commerciale(s)

Configurer une base de données exemple (SQLBookShop)

Nous créons un exemple de base de données simple à deux tables appelée "SQLBookShop" qui contient les enregistrements de tous les livres commandés comme indiqué ci-dessous :

Créez un exemple de base de données SQLBookShop comme suit :

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

Créez et remplissez les objets de base de données (tables) comme suit :

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

Vérification rapide – Exemple de base de données

Effectuez une vérification rapide de la base de données en exécutant la vue OrderedBooks à l'aide du code suivant :

USE SQLBookShop

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

Veuillez noter que j'utilise dbForge Studio pour SQL Server, donc l'aspect de la sortie peut différer si vous exécutez le même code dans SSMS (SQL Server Management Studio). Cependant, il n'y a aucune différence entre les scripts et leurs résultats.

Exigence commerciale pour voir la dernière commande avec des informations supplémentaires

Une exigence commerciale a été envoyée à l'équipe de développement indiquant que "l'utilisateur final souhaite connaître la commande la plus récente passée pour un livre particulier, ainsi que les informations indiquant si la commande a été passée un jour de semaine ou un week-end"

Un mot sur TDDD

Nous ne suivons pas strictement le développement de base de données piloté par les tests (TDDD) dans cet article, mais je recommande fortement d'utiliser le développement de base de données piloté par les tests (TDDD) pour créer à la fois des procédures principales et utilitaires qui commencent par créer un test unitaire pour vérifier si l'objet existe qui échoue dans un premier temps, suivi de la création de l'objet et de la réexécution du test unitaire qui doit réussir.
Pour une présentation détaillée, veuillez vous reporter à la première partie de cet article.

Procédure d'identification de l'utilitaire

En voyant les besoins de l'entreprise, une chose est sûre, nous avons besoin d'une procédure utilitaire qui peut nous dire si une date particulière est un jour de semaine ou un week-end.

Création d'une procédure utilitaire (GetDayType)

Créez une procédure utilitaire et appelez-la "GetDayType" comme suit :

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

Vérification rapide - Procédure utilitaire

Écrivez les lignes de code suivantes pour vérifier rapidement la procédure de l'utilitaire :

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

Création de la procédure principale (GetLatestOrderByBookId)

Créez la procédure principale pour voir la commande la plus récente passée pour un livre particulier et également si la commande a été passée un jour de semaine ou un week-end et appelez-la "GetLatestOrderByBookId" qui contient l'appel de la procédure utilitaire comme suit :

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

Vérification rapide - Procédure principale

Exécutez le code suivant pour voir si la procédure fonctionne correctement ou non :

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

Procédure principale de test unitaire Procédure d'appel de l'utilitaire

La clé ici est de comprendre la différence entre les tests unitaires de la procédure principale et la procédure utilitaire.

Nous nous concentrons actuellement sur les tests unitaires de la procédure principale, ce qui signifie que la procédure utilitaire doit être soigneusement isolée de ce test unitaire.

Utilisation de la procédure d'espionnage

Afin de s'assurer que le test unitaire de la procédure principale reste concentré sur le test de la fonctionnalité de la procédure principale, nous devons utiliser la procédure d'espionnage fournie par tSQLt qui va agir comme un stub (espace réservé) pour la procédure utilitaire.

Selon tsqlt.org, rappelez-vous que si vous espionnez une procédure, vous ne testez pas réellement cette procédure, mais vous facilitez le test unitaire de l'autre procédure liée à la procédure que vous espionnez.

Par exemple, dans notre cas, si nous voulons tester unitairement la procédure principale, nous devons nous moquer de la procédure utilitaire en utilisant une procédure d'espionnage qui nous facilitera le test unitaire de la procédure principale.

Création d'un test unitaire pour la procédure principale de l'utilitaire d'espionnage

Créez un test unitaire de base de données pour vérifier correctement les fonctions de la procédure principale.

Cet article fonctionne pour dbForge Studio for SQL Server (ou uniquement dbForge Unit Test) et SSMS (SQL Server Management Studio) . Cependant, veuillez noter que lorsque vous utilisez SSMS (SQL Server Management Studio), je suppose que vous avez déjà installé tSQLt Framework et que vous êtes prêt à écrire les tests unitaires.

Pour créer le premier test unitaire de base de données, cliquez avec le bouton droit sur la base de données SQLBookShop. Dans le menu contextuel, cliquez sur Test unitaire puis sur Ajouter un nouveau test comme suit :

Écrivez le code du test unitaire :

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

Exécution du test unitaire pour la procédure principale

Exécutez le test unitaire :

Félicitations, vous avez réussi le test unitaire d'une procédure stockée en l'isolant de sa procédure utilitaire après avoir utilisé la procédure d'espionnage.

Pour plus d'informations sur les tests unitaires, veuillez consulter les parties suivantes de mon article précédent sur le développement de bases de données pilotées par les tests (TDDD) :

  • Démarrer le développement de bases de données pilotées par les tests (TDDD) – Partie 1
  • Démarrer le développement de bases de données pilotées par les tests (TDDD) – Partie 2
  • Démarrer le développement de bases de données pilotées par les tests (TDDD) – Partie 3

Choses à faire

Vous pouvez désormais créer des tests unitaires de base de données pour des scénarios légèrement complexes dans lesquels des procédures stockées appellent des procédures utilitaires.

  1. Veuillez essayer de changer l'argument (valeur) de la procédure d'espionnage @CommandToExecute en tant que @CommandToExecute ='set @DayType =”Nothing” ' et voyez que le test va échouer maintenant
  2. Veuillez essayer de répondre aux exigences commerciales de cet article en utilisant le développement de base de données piloté par les tests (TDDD)
  3. Veuillez essayer de répondre à une autre exigence commerciale pour voir la commande la plus récente passée par un client utilisant le développement piloté par les tests (TDDD) impliquant la même procédure utilitaire
  4. Veuillez essayer de créer un test unitaire pour la procédure utilitaire en isolant la procédure principale
  5. Veuillez essayer de créer un test unitaire pour une procédure qui appelle deux procédures utilitaires

Outil utile :

dbForge Unit Test - une interface graphique intuitive et pratique pour la mise en œuvre de tests unitaires automatisés dans SQL Server Management Studio.