Database
 sql >> Base de Dados >  >> RDS >> Database

Simplificando o procedimento armazenado principal do teste de unidade que também chama um procedimento de utilidade


Este artigo fornece um passo a passo da unidade de banco de dados testando um procedimento armazenado que contém um procedimento utilitário dentro dele.

Neste artigo, discutirei um cenário de teste de unidade de banco de dados quando um procedimento armazenado principal depende de um procedimento de utilitário e o procedimento principal precisa ser testado em unidade para garantir que os requisitos sejam atendidos. A chave é garantir que um teste de unidade só possa ser escrito para uma única unidade de código, o que significa que precisamos de um teste de unidade para o procedimento principal e outro teste de unidade para o procedimento utilitário.

O teste de unidade de um único procedimento armazenado é mais fácil em comparação com o teste de unidade de um procedimento que chama um procedimento de utilitário dentro de seu código.

É muito importante entender o cenário do procedimento do utilitário e por que ele é diferente do teste de unidade de um procedimento armazenado normal.

Cenário:Procedimento de Utilidade dentro do Procedimento Principal


Para entender o cenário do procedimento de utilidade, vamos começar pela definição e exemplo de procedimento de utilidade:

O que é Procedimento de Utilidade


Um procedimento utilitário é geralmente um pequeno procedimento usado pelo(s) procedimento(s) principal(is) para realizar alguma tarefa específica, como obter algo para o procedimento principal ou adicionar algo ao procedimento principal.

Outra definição de procedimento utilitário é um pequeno procedimento armazenado escrito para fins de manutenção que pode envolver tabelas ou visualizações do sistema a serem chamadas por qualquer número de procedimentos ou mesmo diretamente.

Exemplos de procedimento de utilidade


Pense em um cenário de pedido de cliente-produto em que um cliente faz um pedido para um determinado produto. Se criarmos o procedimento principal para obter todos os pedidos feitos por um determinado cliente, um procedimento utilitário pode ser usado para nos ajudar a entender se cada pedido foi feito pelo cliente no dia da semana ou no fim de semana.
Desta forma, um pequeno procedimento de utilidade pode ser escrito para retornar “Dia da semana” ou “Fim de semana” com base na data em que o produto foi pedido pelo cliente.

Outro exemplo pode ser procedimentos armazenados no sistema, como “sp_server_info” no banco de dados mestre, que fornece informações sobre a versão instalada do SQL Server:
EXEC sys.sp_server_info


Por que o procedimento do utilitário de teste de unidade é diferente


Conforme discutido anteriormente, o teste de unidade de um procedimento utilitário que é chamado dentro do procedimento principal é um pouco mais complicado do que o teste de unidade de um procedimento armazenado simples.

Considerando o exemplo do produto do pedido do cliente mencionado acima, precisamos escrever um teste de unidade para verificar se o procedimento do utilitário está funcionando bem e também um teste de unidade deve ser escrito para verificar se o procedimento principal que chama o procedimento do utilitário também está funcionando corretamente, além de atender o(s) requisito(s) de negócios.

Isso é ilustrado a seguir:


Isolando do Desafio de Utilidade/Procedimento Principal


O principal desafio ao escrever um(s) teste(s) de unidade para o procedimento que envolve um procedimento de utilidade é ter certeza de que não devemos nos preocupar com o funcionamento do procedimento de utilidade ao escrever um teste de unidade para o procedimento principal e o mesmo vale para o procedimento de utilidade . Esta é uma tarefa desafiadora que deve ser mantida em mente ao escrever testes de unidade para tal cenário.
Isolar do utilitário ou procedimento principal é uma obrigação, dependendo de qual procedimento está sendo testado. Temos que manter as seguintes coisas em mente no contexto de isolamento durante o teste de unidade:
  1. Isolando do procedimento utilitário ao testar a unidade do procedimento principal.
  2. Isolando do procedimento principal quando o procedimento do utilitário de teste de unidade.

Lembre-se de que este artigo se concentra no teste de unidade do procedimento principal, isolando-o de seu procedimento utilitário.

Criando Procedimento Principal e seu Procedimento Utilitário


Para escrever um teste de unidade para um cenário em que o procedimento utilitário está em uso pelo procedimento principal, primeiro precisamos ter os seguintes pré-requisitos:
  1. Banco de dados de amostra
  2. Requisitos comerciais

Configurar banco de dados de exemplo (SQLBookShop)


Estamos criando um banco de dados simples de duas tabelas chamado “SQLBookShop” que contém os registros de todos os livros ordenados conforme mostrado abaixo:



Crie o banco de dados de amostra SQLBookShop da seguinte maneira:
-- (1) Create SQLBookShop database
  CREATE DATABASE SQLBookShop;
  GO

Crie e preencha objetos de banco de dados (tabelas) da seguinte forma:
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

Verificação Rápida – Banco de Dados de Amostra


Faça uma verificação rápida do banco de dados executando a visualização OrderedBooks usando o seguinte código:
USE SQLBookShop

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



Observe que estou usando o dbForge Studio para SQL Server, portanto, a aparência da saída pode ser diferente se você executar o mesmo código no SSMS (SQL Server Management Studio). No entanto, não há diferença entre os scripts e seus resultados.

Requisito comercial para ver o pedido mais recente com informações adicionais


Um requisito de negócios foi enviado à equipe de desenvolvimento informando que "O usuário final deseja saber sobre o pedido mais recente feito para um livro específico, juntamente com as informações se o pedido foi feito em um dia de semana ou fim de semana"

Uma palavra sobre TDDD


Não estamos seguindo estritamente o desenvolvimento de banco de dados orientado a testes (TDDD) neste artigo, mas recomendo fortemente usar o desenvolvimento de banco de dados orientado a testes (TDDD) para criar procedimentos principais e utilitários que começam criando um teste de unidade para verificar se o objeto existe. falha no início, seguido pela criação do objeto e reexecução do teste de unidade que deve passar.
Para um passo a passo detalhado, consulte a primeira parte deste artigo.

Identificando o Procedimento de Utilidade


Vendo o requisito de negócios, uma coisa é certa:precisamos de um procedimento utilitário que possa nos dizer se uma determinada data é um dia da semana ou um fim de semana.

Procedimento de criação de utilitário (GetDayType)


Crie um procedimento utilitário e chame-o de “GetDayType” da seguinte forma:
-- 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

Verificação Rápida - Procedimento de Utilidade


Escreva as seguintes linhas de código para verificar rapidamente o procedimento do utilitário:
-- Quick check utility procedure
declare @DayType varchar(10)
EXEC uspGetDayType '20181001',@DayType output
select @DayType AS [Type of Day]


Criando procedimento principal (GetLatestOrderByBookId)


Crie o procedimento principal para ver o pedido mais recente feito para um determinado livro e também se o pedido foi feito em um dia da semana ou fim de semana e chame-o de “GetLatestOrderByBookId” que contém a chamada para o procedimento utilitário da seguinte forma:
-- 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

Verificação Rápida – Procedimento Principal


Execute o seguinte código para ver se o procedimento está funcionando bem ou não:
-- Get latest order for the bookid=6
EXEC uspGetLatestOrderByBookId @BookId = 6


Procedimento principal de teste de unidade Procedimento de chamada do utilitário


A chave aqui é entender a diferença entre o teste de unidade do procedimento principal e o procedimento utilitário.

No momento, estamos focados em testar a unidade do procedimento principal, portanto, isso significa que o procedimento utilitário precisa ser isolado desse teste de unidade.

Uso do procedimento de espionagem


Para garantir que o teste de unidade do procedimento principal permaneça focado em testar a funcionalidade do procedimento principal, temos que usar o procedimento spy fornecido pelo tSQLt que atuará como um stub (espaço reservado) para o procedimento utilitário.

De acordo com tsqlt.org, por favor, lembre-se que se você estiver espionando um procedimento, você não está realmente testando a unidade desse procedimento, mas sim tornando mais fácil para o outro procedimento relacionado ao procedimento que você está espionando ser testado por unidade.

Por exemplo, no nosso caso, se quisermos fazer o teste unitário do procedimento principal, teremos que zombar do procedimento utilitário usando o procedimento spy, o que facilitará o teste unitário do procedimento principal.

Criação de teste de unidade para o procedimento do utilitário de espionagem do procedimento principal


Crie um teste de unidade de banco de dados para verificar as funções do procedimento principal corretamente.

Este artigo funciona para dbForge Studio para SQL Server (ou apenas dbForge Unit Test) e SSMS (SQL Server Management Studio) . No entanto, observe que, ao usar o SSMS (SQL Server Management Studio), presumo que você já tenha instalado o tSQLt Framework e esteja pronto para escrever os testes de unidade.

Para criar o primeiro teste de unidade de banco de dados, clique com o botão direito do mouse no banco de dados SQLBookShop. No menu de atalho, clique em Unit Test e, em seguida, Add New Test da seguinte forma:



Escreva o código do teste de unidade:
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

Executando o teste de unidade para o procedimento principal


Execute o teste de unidade:



Parabéns, você testou com sucesso um procedimento armazenado isolando-o de seu procedimento utilitário após usar o procedimento de espionagem.

Para obter mais informações sobre testes de unidade, consulte as seguintes partes do meu artigo anterior sobre desenvolvimento de banco de dados orientado a testes (TDDD):
  • Ir para o desenvolvimento de banco de dados orientado a testes (TDDD) – parte 1
  • Ir para o desenvolvimento de banco de dados orientado a testes (TDDD) – parte 2
  • Ir para o desenvolvimento de banco de dados orientado a testes (TDDD) – parte 3

Coisas para fazer


Agora você pode criar testes de unidade de banco de dados para cenários ligeiramente complexos em que procedimentos armazenados chamam procedimentos de utilitário.
  1. Por favor, tente alterar o procedimento de espionagem @CommandToExecute argumento (valor) como @CommandToExecute =‘set @DayType =”Nothing” ‘ e veja que o teste vai falhar agora
  2. Tente atender ao requisito comercial neste artigo usando o desenvolvimento de banco de dados orientado a testes (TDDD)
  3. Tente atender a outro requisito comercial para ver o pedido mais recente feito por qualquer cliente usando desenvolvimento orientado a testes (TDDD) envolvendo o mesmo procedimento de utilidade
  4. Tente criar um teste de unidade para o procedimento utilitário isolando o procedimento principal
  5. Por favor, tente criar um teste de unidade para um procedimento que chama dois procedimentos utilitários


Ferramenta útil:


dbForge Unit Test – uma GUI intuitiva e conveniente para implementar testes de unidade automatizados no SQL Server Management Studio.