sql >> Base de Datos >  >> RDS >> Database

Simplificación del procedimiento almacenado principal de prueba unitaria que también llama a un procedimiento de utilidad

Este artículo proporciona un tutorial de la unidad de base de datos que prueba un procedimiento almacenado que contiene un procedimiento de utilidad dentro de él.

En este artículo, voy a discutir un escenario de prueba de unidad de base de datos cuando un procedimiento almacenado principal depende de un procedimiento de utilidad y el procedimiento principal necesita ser probado por unidad para asegurar que se cumplan los requisitos. La clave es asegurarse de que una prueba de unidad solo se pueda escribir para una sola unidad de código, lo que significa que necesitamos una prueba de unidad para el procedimiento principal y otra prueba de unidad para el procedimiento de utilidad.

La prueba unitaria de un solo procedimiento almacenado es más fácil en comparación con la prueba unitaria de un procedimiento que llama a un procedimiento de utilidad dentro de su código.

Es muy importante comprender el escenario del procedimiento de utilidad y por qué es diferente a la prueba unitaria de un procedimiento almacenado normal.

Escenario:Procedimiento de utilidad dentro del procedimiento principal

Para comprender el escenario del procedimiento de utilidad, comencemos con la definición y el ejemplo del procedimiento de utilidad:

¿Qué es el procedimiento de utilidad?

Un procedimiento de utilidad es generalmente un procedimiento pequeño que utilizan los procedimientos principales para realizar una tarea específica, como obtener algo para el procedimiento principal o agregar algo al procedimiento principal.

Otra definición de procedimiento de utilidad es un pequeño procedimiento almacenado escrito con fines de mantenimiento que puede involucrar tablas o vistas del sistema para ser llamado por cualquier cantidad de procedimientos o incluso directamente.

Ejemplos de procedimiento de utilidad

Piense en un escenario de producto de pedido de cliente en el que un cliente realiza un pedido de un producto en particular. Si creamos el procedimiento principal para obtener todos los pedidos realizados por un cliente en particular, se puede usar un procedimiento de utilidad para ayudarnos a comprender si cada pedido fue realizado por el cliente en un día laborable o en un fin de semana.
De esta manera, un El procedimiento de utilidad pequeña se puede escribir para devolver "Día de la semana" o "Fin de semana" en función de la fecha en que el cliente solicitó el producto.

Otro ejemplo pueden ser los procedimientos almacenados del sistema como "sp_server_info" en la base de datos maestra que proporciona información sobre la versión instalada de SQL Server:

EXEC sys.sp_server_info

Por qué el procedimiento de la utilidad de prueba unitaria es diferente

Como se discutió anteriormente, la prueba unitaria de un procedimiento de utilidad que se llama dentro del procedimiento principal es un poco más complicada que la prueba unitaria de un procedimiento almacenado simple.

Teniendo en cuenta el ejemplo de producto de pedido de cliente mencionado anteriormente, necesitamos escribir una prueba unitaria para verificar que el procedimiento de utilidad funcione bien y también se debe escribir una prueba unitaria para verificar que el procedimiento principal que llama al procedimiento de utilidad también funciona correctamente y cumple los requisitos comerciales.

Esto se ilustra de la siguiente manera:

Aislamiento de la utilidad/desafío del procedimiento principal

El principal desafío al escribir una(s) prueba(s) unitaria(s) para el procedimiento que implica un procedimiento de utilidad es asegurarse de que no debemos preocuparnos por el funcionamiento del procedimiento de utilidad al escribir una prueba unitaria para el procedimiento principal y lo mismo es cierto para el procedimiento de utilidad . Esta es una tarea desafiante que debe tenerse en cuenta al escribir pruebas unitarias para tal escenario.
Aislar de la utilidad o del procedimiento principal es imprescindible, según el procedimiento que se esté probando. Debemos tener en cuenta lo siguiente en el contexto del aislamiento durante las pruebas unitarias:

  1. Aislamiento del procedimiento de utilidad cuando la unidad prueba el procedimiento principal.
  2. Aislamiento del procedimiento principal cuando se realiza la prueba unitaria del procedimiento de utilidad.

Recuerde que este artículo se centra en la prueba unitaria del procedimiento principal aislándolo de su procedimiento de utilidad.

Creación del procedimiento principal y su procedimiento de utilidad

Para escribir una prueba unitaria para un escenario donde el procedimiento de utilidad está en uso por el procedimiento principal, primero debemos tener los siguientes requisitos previos:

  1. Base de datos de muestra
  2. Requisito(s) comercial(es)

Configurar base de datos de muestra (SQLBookShop)

Estamos creando una base de datos de muestra simple de dos tablas llamada "SQLBookShop" que contiene los registros de todos los libros ordenados como se muestra a continuación:

Cree una base de datos de muestra de SQLBookShop de la siguiente manera:

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

Cree y complete los objetos de la base de datos (tablas) de la siguiente manera:

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

Comprobación rápida:base de datos de muestra

Realice una comprobación rápida de la base de datos ejecutando la vista OrderedBooks utilizando el siguiente código:

USE SQLBookShop

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

Tenga en cuenta que estoy usando dbForge Studio para SQL Server, por lo que el aspecto de salida puede diferir si ejecuta el mismo código en SSMS (SQL Server Management Studio). Sin embargo, no hay diferencia entre los scripts y sus resultados.

Requisito comercial para ver el último pedido con información adicional

Se envió un requisito comercial al equipo de desarrollo que establece que "El usuario final desea conocer el pedido más reciente realizado para un libro en particular junto con la información de si el pedido se realizó entre semana o fin de semana"

Una palabra sobre TDDD

No seguimos estrictamente el desarrollo de bases de datos basado en pruebas (TDDD) en este artículo, pero recomiendo encarecidamente utilizar el desarrollo de bases de datos basado en pruebas (TDDD) para crear procedimientos principales y de utilidad que comienzan con la creación de una prueba unitaria para verificar si existe un objeto que falla al principio, luego se crea el objeto y se vuelve a ejecutar la prueba unitaria que debe pasar.
Para obtener un recorrido detallado, consulte la primera parte de este artículo.

Identificación del procedimiento de utilidad

Al ver el requisito comercial, una cosa es segura:necesitamos un procedimiento de utilidad que pueda decirnos si una fecha en particular es un día de semana o un fin de semana.

Creación de procedimiento de utilidad (GetDayType)

Cree un procedimiento de utilidad y llámelo "GetDayType" de la siguiente manera:

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

Comprobación rápida:procedimiento de utilidad

Escriba las siguientes líneas de código para verificar rápidamente el procedimiento de utilidad:

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

Creación del procedimiento principal (GetLatestOrderByBookId)

Cree el procedimiento principal para ver el pedido más reciente realizado para un libro en particular y también si el pedido se realizó en un día laborable o un fin de semana y llámelo "GetLatestOrderByBookId", que contiene la llamada para el procedimiento de utilidad de la siguiente manera:

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

Comprobación rápida:procedimiento principal

Ejecute el siguiente código para ver si el procedimiento funciona bien o no:

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

Procedimiento principal de prueba unitaria Procedimiento de utilidad de llamada

La clave aquí es comprender la diferencia entre la prueba unitaria del procedimiento principal y el procedimiento de utilidad.

Actualmente estamos enfocados en la prueba unitaria del procedimiento principal, por lo que esto significa que el procedimiento de utilidad debe aislarse con gracia de esta prueba unitaria.

Uso del procedimiento de espionaje

Para asegurarnos de que la prueba de la unidad del procedimiento principal permanezca enfocada en probar la funcionalidad del procedimiento principal, tenemos que usar el procedimiento de espionaje provisto por tSQLt que actuará como un stub (marcador de posición) para el procedimiento de utilidad.

De acuerdo con tsqlt.org, recuerde que si está espiando un procedimiento, en realidad no está probando la unidad de ese procedimiento, sino que está facilitando que el otro procedimiento relacionado con el procedimiento que está espiando sea probado por unidad.

Por ejemplo, en nuestro caso, si queremos realizar una prueba unitaria del procedimiento principal, tenemos que simular el procedimiento de utilidad mediante el uso de un procedimiento espía que nos facilitará la prueba unitaria del procedimiento principal.

Creación de prueba de unidad para el procedimiento de utilidad de espionaje de procedimiento principal

Cree una prueba de unidad de base de datos para verificar que el procedimiento principal funcione correctamente.

Este artículo funciona para dbForge Studio para SQL Server (o solo dbForge Unit Test) y SSMS (SQL Server Management Studio) . Sin embargo, tenga en cuenta que al usar SSMS (SQL Server Management Studio), asumo que ya instaló tSQLt Framework y está listo para escribir las pruebas unitarias.

Para crear la primera prueba unitaria de la base de datos, haga clic con el botón derecho en la base de datos SQLBookShop. En el menú contextual, haga clic en Prueba unitaria y luego Agregar nueva prueba de la siguiente manera:

Escriba el código de prueba de la unidad:

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

Ejecución de prueba unitaria para el procedimiento principal

Ejecute la prueba unitaria:

Felicitaciones, ha probado con éxito un procedimiento almacenado aislándolo de su procedimiento de utilidad después de usar el procedimiento de espionaje.

Para obtener más información sobre las pruebas unitarias, consulte las siguientes partes de mi artículo anterior sobre el desarrollo de bases de datos basadas en pruebas (TDDD):

  • Pase a comenzar el desarrollo de bases de datos basadas en pruebas (TDDD):parte 1
  • Pase a comenzar el desarrollo de bases de datos basadas en pruebas (TDDD):parte 2
  • Pase a comenzar el desarrollo de bases de datos basadas en pruebas (TDDD):parte 3

Cosas que hacer

Ahora puede crear pruebas unitarias de base de datos para escenarios ligeramente complejos donde los procedimientos almacenados llaman a procedimientos de utilidad.

  1. Intente cambiar el procedimiento de espionaje @CommandToExecute argumento (valor) como @CommandToExecute ='set @DayType ='Nothing' ' y verá que la prueba fallará ahora
  2. Intente cumplir con los requisitos comerciales de este artículo mediante el desarrollo de bases de datos basadas en pruebas (TDDD)
  3. Intente cumplir con otro requisito comercial para ver el pedido más reciente realizado por cualquier cliente que utilice el desarrollo basado en pruebas (TDDD) que involucra el mismo procedimiento de utilidad
  4. Intente crear una prueba unitaria para el procedimiento de utilidad aislando el procedimiento principal
  5. Pruebe usted mismo a crear una prueba unitaria para un procedimiento que llame a dos procedimientos de utilidad

Herramienta útil:

Prueba unitaria de dbForge:una GUI intuitiva y conveniente para implementar pruebas unitarias automatizadas en SQL Server Management Studio.