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

Ir para o início do desenvolvimento de banco de dados orientado a testes (TDDD)


Via de regra, começamos a desenvolver soluções de banco de dados criando objetos de banco de dados, como tabelas, visualizações, procedimentos armazenados, etc., com base nos requisitos do negócio. Essa abordagem também é conhecida como Desenvolvimento de banco de dados convencional . Neste artigo, vamos explorar essa abordagem e ilustrá-la com exemplos.


Desenvolvimento de banco de dados convencional


O estilo de desenvolvimento consiste nas seguintes etapas:
  1. Receba os requisitos
  2. Criar objetos de banco de dados com base nos requisitos
  3. Execute testes de unidade para objetos de banco de dados para ver se eles atendem aos requisitos
  4. Receber novos requisitos
  5. Modifique objetos de banco de dados existentes ou adicione novos para atender aos novos requisitos
  6. Crie e execute testes de unidade para verificar se os novos requisitos funcionam adequadamente e não entram em conflito com os anteriores



Para explorar e ilustrar os processos, vamos começar configurando um banco de dados de amostra SQLDevBlog :
 
-- Create sample database (SQLDevBlog)
  CREATE DATABASE SQLDevBlog

Use o código a seguir para criar tabelas nesse banco de dados de exemplo:
USE SQLDevBlog;

-- (1) Create Author table in the sample database
CREATE TABLE Author (
  AuthorId INT PRIMARY KEY IDENTITY (1, 1)
 ,Name VARCHAR(40)
 ,RegistrationDate DATETIME2
 ,Notes VARCHAR(400)
)

-- (2) Create Article Category table in the sample database
CREATE TABLE Category (
  CategoryId INT PRIMARY KEY IDENTITY (1, 1)
 ,Name VARCHAR(50)
 ,Notes VARCHAR(400)
)

-- (3) Create Article table in the sample database
CREATE TABLE Article (
  ArticleId INT PRIMARY KEY IDENTITY (1, 1)
 ,CategoryId INT
 ,AuthorId INT
 ,Title VARCHAR(150)
 ,Published DATETIME2
 ,Notes VARCHAR(400)  
)

-- Adding foreign keys for author and article category
ALTER TABLE Article ADD CONSTRAINT FK_Category_CategoryId FOREIGN KEY (CategoryId) REFERENCES Category (CategoryId)
ALTER TABLE Article ADD CONSTRAINT FK_Author_AuthorId FOREIGN KEY (AuthorId) REFERENCES Author (AuthorId)

GO

Revise o diagrama de banco de dados com nossas tabelas recém-criadas:

Observação :Estou usando aqui o dbForge Studio for SQL Server para fazer todas as tarefas. A aparência de sua saída pode ser diferente do SSMS (SQL Server Management Studio), mas os resultados são os mesmos.

Em seguida, preencheremos nosso SQLDevBlog banco de dados de exemplo para criar um cenário mais realista:
-- (5) Populating Author table
INSERT INTO Author (Name, RegistrationDate, Notes)
  VALUES ('Sam', '2017-01-01', 'Database Analyst'),
  ('Asif', '2017-01-02', 'Database and Business Intelligence Developer'),
  ('Sadaf', '2018-01-01', 'Database Analyst Programmer')

-- (6) Populating Category table
INSERT INTO Category (Name, Notes)
  VALUES ('Development', 'Articles about database development'),
  ('Testing', 'Database testing related articles'),
  ('DLM', 'Database lifecycle management')

-- (7) Populating Article 
INSERT INTO Article (CategoryId, AuthorId, Title, Published, Notes)
  VALUES (1, 1, 'Fundamentals of SQL Database Development', '02-01-2018', ''),
  (1, 2, 'Advanced Database Development', '02-01-2018', ''),
  (2, 3, 'All About Database Testing', '03-01-2018', '');
GO

Como resultado, temos as seguintes tabelas preenchidas:

Agora que terminamos a configuração do banco de dados e o preenchimento da tabela, estamos na próxima etapa. Temos que imitar o cenário com um novo requisito.

O requisito para adicionar uma nova categoria


Um novo requisito afirma que um administrador deve poder adicionar uma nova categoria à lista de categorias disponíveis . Para atender a esse requisito, sua equipe de desenvolvimento precisa criar um procedimento armazenado para adicionar um novo requisito com facilidade. Ou temos que criar um AddCategory Objeto de banco de dados.

Para criar o procedimento armazenado, execute o seguinte script:
-- (8) This procedure meets a new requirement by adding a new category
CREATE PROCEDURE dbo.AddCategory @CategoryName VARCHAR(50),
@Notes VARCHAR(400)
AS
  INSERT INTO Category (Name, Notes)
    VALUES (@CategoryName, @Notes);
GO

O resultado é o seguinte:

Criar teste de unidade de banco de dados para verificar se o procedimento funciona corretamente


A próxima etapa é criar o teste de unidade do banco de dados para verificar se o procedimento armazenado atende à especificação.

Esta dica funciona para dbForge Studio para SQL Server (ou apenas teste de unidade dbForge ) e SSMS (SQL Server Management Studio) . Observação:ao usar o SSMS (SQL Server Management Studio), certifique-se de instalar o tSQLt Framework para escrever os testes unitários.

Para criar o primeiro teste de unidade de banco de dados, clique com o botão direito do mouse no SQLDevBlog banco de dados> Teste de unidade > Adicionar novo teste

O Adicionar novo teste janela se abre. Preencha todas as informações necessárias e clique em Adicionar teste .

Crie o teste de unidade da seguinte forma e salve-o:
--  Comments here are associated with the test.
--  For test case examples, see: http://tsqlt.org/user-guide/tsqlt-tutorial/
CREATE PROCEDURE AddCategoryTests.[test to check if AddCategory procedure works]
AS
BEGIN
  --Assemble
  EXEC tSQLt.FakeTable @TableName = 'dbo.Category' -- create an empty dependency free Category table
                    

  
  CREATE TABLE AddCategoryTests.Expected ( -- create expected table 
  CategoryId INT 
 ,Name VARCHAR(50) NULL
 ,Notes VARCHAR(400) NULL
  ) 
                      
  INSERT INTO AddCategoryTests.Expected (CategoryId,Name, Notes) -- Insert data into expected table
  VALUES (null,'Database Dummy Category', 'This is just a dummy category for testing');
  
  --Act
  EXEC AddCategory @CategoryName = 'Database Dummy Category' 
                  ,@Notes = 'This is just a dummay category for testing'

   
  --Assert
  EXEC tSQLt.AssertEqualsTable @Expected = 'AddCategoryTests.Expected'
                              ,@Actual = 'dbo.Category'
                           

END;
GO

Clique no Banco de dados menu> Teste Unitário > Ver lista de testes e execute o teste de unidade conforme mostrado abaixo:

Podemos ver que o teste de unidade foi bem-sucedido. Assim, uma nova categoria pode ser adicionada ao banco de dados (tabela). O requisito foi atendido.

Agora, vamos explorar o desenvolvimento de banco de dados orientado a testes e descrever como o processo de escrever testes de unidade pode satisfazer os requisitos.

Desenvolvimento de banco de dados orientado a testes (TDDD)


O desenvolvimento de banco de dados orientado a testes (TDDD) começa com a gravação do teste de unidade que falhará primeiro. Em seguida, vamos modificá-lo para passar e refiná-lo.

Os testes de unidade são escritos para atender aos requisitos e testes de unidade que exigem que os objetos de banco de dados sejam criados e executados corretamente.

Para entender a diferença entre o desenvolvimento de banco de dados tradicional e o desenvolvimento de banco de dados orientado a testes, vamos criar o SQLDevBlogTDD base de dados. É o mesmo que SQLDevBlog .
-- Create sample database (SQLDevBlogTDD)
  CREATE DATABASE SQLDevBlogTDD

Em seguida, preencha o banco de dados de amostra com tabelas:
USE SQLDevBlogTDD;

-- (1) Create Author table in the sample database
CREATE TABLE Author (
  AuthorId INT PRIMARY KEY IDENTITY (1, 1)
 ,Name VARCHAR(40)
 ,RegistrationDate DATETIME2
 ,Notes VARCHAR(400)
)

-- (2) Create Article Category table in the sample database
CREATE TABLE Category (
  CategoryId INT PRIMARY KEY IDENTITY (1, 1)
 ,Name VARCHAR(50)
 ,Notes VARCHAR(400)
)

-- (3) Create Article table in the sample database
CREATE TABLE Article (
  ArticleId INT PRIMARY KEY IDENTITY (1, 1)
 ,CategoryId INT
 ,AuthorId INT
 ,Title VARCHAR(150)
 ,Published DATETIME2
 ,Notes VARCHAR(400)  
)

-- Adding foreign keys for author and article category
ALTER TABLE Article ADD CONSTRAINT FK_Category_CategoryId FOREIGN KEY (CategoryId) REFERENCES Category (CategoryId)
ALTER TABLE Article ADD CONSTRAINT FK_Author_AuthorId FOREIGN KEY (AuthorId) REFERENCES Author (AuthorId)

GO

Temos que preencher nosso banco de dados de amostra para criar um cenário mais realista da seguinte forma:
-- Use SQLDevBlogTDD
-- (5) Populating Author table
INSERT INTO Author (Name, RegistrationDate, Notes)
  VALUES ('Sam', '2017-01-01', 'Database Analyst'),
  ('Asif', '2017-01-02', 'Database and Business Intelligence Developer'),
  ('Sadaf', '2018-01-01', 'Database Analyst Programmer')

-- (6) Populating Category table
INSERT INTO Category (Name, Notes)
  VALUES ('Development', 'Articles about database development'),
  ('Testing', 'Database testing related articles'),
  ('DLM', 'Database lifecycle management')

-- (7) Populating Article 
INSERT INTO Article (CategoryId, AuthorId, Title, Published, Notes)
  VALUES (1, 1, 'Fundamentals of SQL Database Development', '02-01-2018', ''),
  (1, 2, 'Advanced Database Development', '02-01-2018', ''),
  (2, 3, 'All About Database Testing', '03-01-2018', '');
GO

Requisito para adicionar nova categoria (TDDD)


Agora, temos o mesmo requisito:o administrador deve poder adicionar uma nova categoria à lista de categorias disponíveis. Para atender ao requisito, precisamos primeiro escrever um teste de unidade de banco de dados que procure um objeto em potencial.

É assim que o TDDD funciona:o teste de unidade primeiro falha, pois assumimos que estamos procurando o objeto que atualmente não existe, mas que estará lá em breve.

Crie e execute o teste de unidade de banco de dados para verificar se o objeto desejado existe


Embora saibamos que não existe agora, pensamos nisso como um ponto de partida.

No dbForge Studio para SQL Server, o teste de unidade de banco de dados que suporta TDDD por padrão é criado para falhar primeiro. Então, vamos mudar um pouco. Se você estiver usando um tSQLt estrutura de teste de unidade de banco de dados diretamente, escreva o seguinte teste de unidade:
--  Comments here are associated with the test.
--  For test case examples, see: http://tsqlt.org/user-guide/tsqlt-tutorial/
CREATE PROCEDURE CategoryTests.[test to check if routine to add new category exists]
AS
BEGIN
  --Assemble
  --  This section is for code that sets up the environment. It often
  --  contains calls to methods such as tSQLt.FakeTable and tSQLt.SpyProcedure
  --  along with INSERTs of relevant data.
  --  For more information, see http://tsqlt.org/user-guide/isolating-dependencies/

  --Act
  --  Execute the code under tests like a stored procedure, function, or view
  --  and capture the results in variables or tables.

  --Assert
  --  Compare the expected and actual values, or call tSQLt.Fail in an IF statement.
  --  Available Asserts: tSQLt.AssertEquals, tSQLt.AssertEqualsString, tSQLt.AssertEqualsTable
  --  For a complete list, see: http://tsqlt.org/user-guide/assertions/
  EXEC tSQLt.AssertObjectExists @ObjectName = N'dbo.AddCategory'
                              

END;
GO

Depois de executar o teste de unidade de banco de dados, você pode ver que o teste falha:

Crie o objeto de banco de dados e execute novamente o teste de unidade


A próxima etapa é criar o objeto de banco de dados necessário. No nosso caso, é um procedimento armazenado.
-- (8) This procedure meets the new requirement by adding a new category
CREATE PROCEDURE dbo.AddCategory @CategoryName VARCHAR(50),
@Notes VARCHAR(400)
AS  
-- Category Procedure Stub (template) in TDDD
GO

Execute o teste de unidade novamente - desta vez é bem-sucedido:

Mas não é suficiente passar no teste de unidade verificando se o procedimento armazenado existe. Também precisamos verificar se o procedimento armazenado adiciona uma nova categoria.

Crie o teste de unidade de banco de dados para verificar se a rotina funciona corretamente


Vamos criar o novo teste de unidade do banco de dados:
--  Comments here are associated with the test.
--  For test case examples, see: http://tsqlt.org/user-guide/tsqlt-tutorial/
CREATE PROCEDURE CategoryTests.[test to check routine adds new category]
AS
BEGIN
  --Assemble
  EXEC tSQLt.FakeTable @TableName = 'dbo.Category' -- create an empty dependency free Category table
                      

  
  CREATE TABLE CategoryTests.Expected ( -- create expected table 
  CategoryId INT 
 ,Name VARCHAR(50) NULL
 ,Notes VARCHAR(400) NULL
  ) 
                      
  INSERT INTO CategoryTests.Expected (CategoryId,Name, Notes) -- Insert data into expected table
  VALUES (null,'Database Dummy Category', 'This is just a dummy category for testing');
  
  --Act
  EXEC AddCategory @CategoryName = 'Database Dummy Category' 
                  ,@Notes = 'This is just a dummay category for testing'

  --SELECT * INTO CategoryTests.Actual FROM Category -- put category table data into an actual table
  
  --Assert
  EXEC tSQLt.AssertEqualsTable @Expected = 'CategoryTests.Expected'
                              ,@Actual = 'dbo.Category'
                           

END;
GO

Como você pode ver, o teste de unidade falha pela primeira vez e é bem-sucedido pela segunda vez:

Adicione funcionalidade à rotina e execute novamente o teste de unidade


Modifique o procedimento armazenado adicionando a funcionalidade necessária para que o teste seja bem-sucedido, conforme mostrado abaixo:
-- (8) This procedure meets the new requirement by adding a new category
ALTER PROCEDURE dbo.AddCategory @CategoryName VARCHAR(50),
@Notes VARCHAR(400)
AS
  INSERT INTO Category (Name, Notes)
    VALUES (@CategoryName, @Notes);
GO

Execute novamente os testes de unidade para verificar se todos foram bem-sucedidos, incluindo o procedimento armazenado recentemente modificado:

Dessa forma, implementamos com sucesso o desenvolvimento de banco de dados orientado a testes. Agora podemos nos concentrar apenas nos requisitos. Os testes de unidade encapsulam os requisitos, exigindo que os objetos de banco de dados sejam criados e executados adequadamente para atender à especificação.

Vamos ver quão eficaz é o TDDD quando se trata de satisfazer um requisito de relatórios de negócios.

Satisfazer o requisito de relatórios de negócios por meio de TDDD


Assumimos que o banco de dados já obteve os objetos necessários (como tabelas) antes de receber o novo requisito de relatório de negócios.

Vamos criar um banco de dados de exemplo chamado SQLDevBlogReportTDD :
-- Create sample database (SQLDevBlogReportTDD)
CREATE DATABASE SQLDevBlogReportTDD;
GO

Em seguida, crie e preencha as tabelas para o banco de dados de exemplo usando o seguinte código:
USE SQLDevBlogReportTDD;
-- (1) Create Author table in the sample database
CREATE TABLE Author (
  AuthorId INT PRIMARY KEY IDENTITY (1, 1)
 ,Name VARCHAR(40)
 ,RegistrationDate DATETIME2
 ,Notes VARCHAR(400)
)

-- (2) Create an Article Category table in the sample database
CREATE TABLE Category (
  CategoryId INT PRIMARY KEY IDENTITY (1, 1)
 ,Name VARCHAR(50)
 ,Notes VARCHAR(400)
)

-- (3) Create Article table in the sample database
CREATE TABLE Article (
  ArticleId INT PRIMARY KEY IDENTITY (1, 1)
 ,CategoryId INT
 ,AuthorId INT
 ,Title VARCHAR(150)
 ,Published DATETIME2
 ,Notes VARCHAR(400)  
)

-- Adding foreign keys for author and article category
ALTER TABLE Article ADD CONSTRAINT FK_Category_CategoryId FOREIGN KEY (CategoryId) REFERENCES Category (CategoryId)
ALTER TABLE Article ADD CONSTRAINT FK_Author_AuthorId FOREIGN KEY (AuthorId) REFERENCES Author (AuthorId)

GO

-- (4) Populating Author table
INSERT INTO Author (Name, RegistrationDate, Notes)
  VALUES ('Peter', '2017-01-01', 'Database Analyst'),
  ('Adil', '2017-01-02', 'Database and Business Intelligence Developer'),
  ('Sarah', '2018-01-01', 'Database Analyst Programmer'),
  ('Asim', '2018-01-01', 'Database Analyst')

-- (5) Populating Category table
INSERT INTO Category (Name, Notes)
  VALUES 
  ('Analysis', 'Database Analysis'),
  ('Development', 'Articles about database development'),
  ('Testing', 'Database testing related articles'),
  ('DLM', 'Database lifecycle management')
 

-- (6) Populating Article 
INSERT INTO Article (CategoryId, AuthorId, Title, Published, Notes)
  VALUES (1, 1, 'Replicating a problem in SQL', '02-01-2018', ''),
  (1, 2, 'Modern Database Development Tools', '02-01-2018', ''),
  (3, 3, 'Test Driven Database Development (TDDD)', '03-01-2018', ''),
  (3, 1, 'Database Unit Testing Fundamentals', '10-01-2018', ''),
  (3, 3, 'Unit Testing with tSQLt', '10-01-2018', '')
GO

Crie uma visualização para ver a lista de todos os autores, artigos e categorias de artigos:
-- (7) Create a view to see a list of authors, articles, and categories
CREATE VIEW dbo.vwAuthors 
AS SELECT a.Name AS AuthorName,a1.Title AS ArticleTitle,c.Name AS CategoryName  FROM Author a INNER JOIN Article a1 ON a.AuthorId = a1.AuthorId INNER JOIN Category c ON a1.CategoryId = c.CategoryId
GO

Execute a visualização da tabela criada para ver os resultados:

Após processar a configuração do banco de dados e preencher as tabelas, o próximo passo é imitar o cenário onde recebemos um novo requisito.

Requisito comercial:número total de artigos por relatório do autor


Considere um novo requisito de relatórios de negócios. Tem que declarar um relatório de banco de dados para visualizar o número total de artigos por autor.

A primeira coisa é atribuir um objeto de banco de dados que possa atender aos requisitos de negócios. No nosso caso, é o ArticlesPerAuthorReport objeto de banco de dados.

Para atender ao requisito, é necessário criar um teste de unidade de banco de dados que procure um possível objeto apropriado. Como sabemos, esse teste falhará primeiro porque procurará o objeto que não existe no momento, mas estará lá em breve.

Plano de implementação de TDDD


De acordo com os padrões de teste de unidade de banco de dados orientado a testes, as seguintes coisas devem estar lá para atender ao requisito de relatório:
  1. Desenvolva um único objeto de banco de dados que atenda aos requisitos de relatório.
  2. Crie um teste de unidade para verificar a existência do objeto.
  3. Crie um stub de objeto (espaço reservado) para passar no primeiro teste.
  4. Crie um segundo teste de unidade para verificar se o objeto gera dados corretos na tabela com a entrada correta.
  5. Modifique a definição do objeto para que o segundo teste seja aprovado.

Crie o teste de unidade de banco de dados para verificar se o objeto desejado existe


Atribuiremos um objeto de banco de dados que pode atender ao requisito de negócios. No nosso caso, é o ArticlesPerAuthorReport objeto de banco de dados. A próxima etapa é criar um teste de unidade de banco de dados.

Para criar o primeiro teste de unidade de banco de dados, clique com o botão direito do mouse em SQLDevBlogReport banco de dados> Teste de unidade > Adicionar novo teste

Escreva o seguinte código para criar o teste de unidade que verifica a existência ou ausência do objeto desejado:
CREATE PROCEDURE ArticlesPerAuthorReport.[test to check ArticlesPerAuthorReport exists]
AS
BEGIN
  --Assemble
 
  --Act
  
  --Assert
   EXEC tSQLt.AssertObjectExists @ObjectName = N'ArticlesPerAuthorReport'
END;
GO

O teste de unidade deve falhar, pois verifica o objeto criado antes. O próprio objeto é criado para cumprir o TDDD:

Criar objeto Stub e executar a unidade


Crie um stub de objeto com alguma saída esperada codificada, pois queremos apenas criar um objeto de banco de dados com o resultado esperado. Crie o Relatório ArticlesPerAuthor objeto como um stub de visão (espaço reservado) em primeiro lugar:
-- (8) Create ArticlesPerAuthorReport view stub
  CREATE VIEW ArticlesPerAuthorReport
    AS
    SELECT 'Adil' AS Author, 10 AS [Total Articles]
    UNION ALL
    SELECT 'Sam' AS Author, 5 AS [Total Articles]

A execução do teste de unidade deve ser bem-sucedida:

A criação de um stub serve como um pontapé inicial para o TDDD. Criamos o objeto para passar no teste e não nos preocupamos com o funcionamento real do objeto.

Crie e execute o teste de unidade para verificar se o objeto gera dados corretos


É hora de verificar se o objeto desejado está funcionando corretamente. Crie outro teste de unidade para verificar a saída de dados pelo objeto desejado (ArticlesPerAuthorReport ). Vamos adicionar um novo teste de unidade ao banco de dados:

Adicione o seguinte código de teste de unidade:
CREATE PROCEDURE ArticlesPerAuthorReport.[test to check ArticlesPerAuthorReport outputs correct]
AS
BEGIN
  --Assemble
  --  Create mocked up tables (blank copies of original tables without constraints and data)
  EXEC tSQLt.FakeTable @TableName = N'Author'
                      ,@SchemaName = N'dbo'

  EXEC tSQLt.FakeTable @TableName = N'Article'
                      ,@SchemaName = N'dbo'                      

  EXEC tSQLt.FakeTable @TableName = N'Category'
                      ,@SchemaName = N'dbo'                      

  -- Add rows to the mocked up tables
  INSERT INTO Author (AuthorId,Name, RegistrationDate, Notes)
  VALUES (1,'Zak', DATEFROMPARTS(2017,01,01), 'Database Author'),
    (2,'Akeel',DATEFROMPARTS(2018,01,01),'Business Intelligence Author')
  
  INSERT INTO Category (CategoryID,Name, Notes)
  VALUES (1,'Database Development', '-'),
  (2,'Business Intelligene','-');

  INSERT INTO Article (ArticleId,CategoryId, AuthorId, Title, Published, Notes)
  VALUES (1,1, 1, 'Advanced Database Development', DATEFROMPARTS(2017,02,01),'10K Views'),
  (1,1, 1, 'Database Development with Cloud Technologies', DATEFROMPARTS(2017,02,01),'5K Views'),
  (1,1, 1, 'Developing Databases with Modern Tools', DATEFROMPARTS(2017,03,01),'20K Views'),
  (1,2, 2, 'Business Intelligence Fundamentals', DATEFROMPARTS(2017,02,01),'10K Views'),
  (1,2, 2, 'Tabular Models', DATEFROMPARTS(2017,02,01),'50K Views')

  -- Create an expected table
  CREATE TABLE ArticlesPerAuthorReport.Expected
  (Author VARCHAR(40),[Total Articles] int)  

  -- Add expected results into an expected table
  INSERT INTO ArticlesPerAuthorReport.Expected (Author, [Total Articles])
  VALUES ('Zak', 3), ('Akeel',2);


  --Act
  --  Run ArticlesPerAuthorReport object (view) and put results into an actual table
  SELECT * INTO ArticlesPerAuthorReport.Actual FROM ArticlesPerAuthorReport apar
  

  --Assert
  --  Compare the expected and actual tables
  EXEC TSQLT.AssertEqualsTable @Expected = N'ArticlesPerAuthorReport.Expected'
                              ,@Actual = N'ArticlesPerAuthorReport.Actual'
                              

END;
GO

Execute o teste de unidade que também deve falhar para cumprir o TDDD:

Adicione a funcionalidade necessária ao objeto ArticlesPerAuthorReport


O teste de unidade que verifica a funcionalidade do objeto requer uma estrutura modificada para que o teste possa passar.

Modifique o ArticlesPerAuthorReport view para deixar o segundo teste de unidade passar como o primeiro:
ALTER VIEW ArticlesPerAuthorReport
  AS
SELECT a.Name AS [Author],COUNT(a1.ArticleId) AS [Total Articles] FROM Author a 
    INNER JOIN Article a1 ON a.AuthorId = a1.AuthorId
    GROUP BY a.Name

O objeto de banco de dados foi modificado com sucesso para gerar os dados desejados. Execute todos os testes de unidade:

O Relatório ArticlesPerAuthor objeto está pronto.

Nossa próxima tarefa é fornecer um passo a passo da criação de uma base de relatório em um objeto de banco de dados desenvolvido e testado usando desenvolvimento orientado a testes (TDDD).

Implementando o requisito de relatório (ArticlesPerAuthorReport)


Primeiro, vamos redefinir o SQLDevBlogReportTDD e adicionar mais dados a ele. Ou você pode criar um banco de dados em branco pela primeira vez.

Para adicionar dados suficientes ao nosso banco de dados de amostra, recupere dados de vwAuthors view para ver todos os registros:

Executando os testes de unidade


Execute os testes de unidade de banco de dados que criamos anteriormente:

Parabéns, ambos os testes foram aprovados, o que significa que o objeto de banco de dados desejado é capaz de atender ao requisito de relatório para visualizar artigos por autor.

Criar relatório de banco de dados com base no objeto ArticlesPerAuthorsReport


Você pode criar um relatório de banco de dados de várias maneiras (como usando o Report Builder, criando o Report Server Project no Visual Studio Data Tools ou usando o dbForge Studio para SQL Server).

Nesta seção, estamos usando o dbForge Studio for SQL Server para criação de relatórios. Para continuar, clique em Novo do Arquivo Menu> Relatório de dados :

Clique em Relatório padrão :

Selecione Tabela simples\Exibição como Tipo de dados :

Adicione o objeto base (ArticlesPerAuthorReport ), que é uma visão no nosso caso:

Adicione os campos obrigatórios:

Não precisamos de nenhum agrupamento neste momento, então continue clicando em Próximo :

Selecione Layout e Orientação do relatório de dados:

Por fim, adicione o título Relatório de artigos por autor e clique em Concluir :

Em seguida, ajuste a formatação do relatório conforme o requisito:

Clique em Visualizar para ver o relatório do banco de dados:

Salve o relatório como ArticlesPerAuthorReport . O relatório do banco de dados foi criado devido aos requisitos de negócios.

Uso do Procedimento de Configuração


Ao escrever testes de unidade de banco de dados usando tSQLt, você verá que alguns códigos de teste são repetidos com frequência. Assim, você pode defini-lo em um procedimento de configuração e reutilizá-lo posteriormente em outros testes de unidade dessa classe de teste específica. Cada classe de teste pode ter apenas um procedimento de configuração que é executado automaticamente antes que os testes de unidade dessa classe sejam processados.

Podemos colocar quase todo o código de teste escrito em Assemble (seção) sob o procedimento de configuração para evitar a duplicação de código.

Por exemplo, precisamos criar tabelas simuladas junto com uma tabela esperada. Em seguida, adicionaremos dados às tabelas simuladas e, em seguida, à tabela esperada. Podemos defini-lo facilmente no procedimento de configuração e reutilizá-lo ainda mais.

Criação do procedimento de configuração para evitar duplicação de código de teste


Crie um procedimento armazenado em SQLDevBlogTDD do seguinte modo:
-- (12) Use of Setup Procedure to avoid repeating common test code
CREATE PROCEDURE ArticlesPerAuthorReport.Setup 
AS 
BEGIN
  --Assemble
  --  Create mocked up tables (blank copies of original tables without constraints and data)
  EXEC tSQLt.FakeTable @TableName = N'Author'
                      ,@SchemaName = N'dbo'

  EXEC tSQLt.FakeTable @TableName = N'Article'
                      ,@SchemaName = N'dbo'                      

  EXEC tSQLt.FakeTable @TableName = N'Category'
                      ,@SchemaName = N'dbo'                      

  -- Add rows to the mocked up tables
  INSERT INTO Author (AuthorId,Name, RegistrationDate, Notes)
  VALUES (1,'Zak', DATEFROMPARTS(2017,01,01), 'Database Author'),
    (2,'Akeel',DATEFROMPARTS(2018,01,01),'Business Intelligence Author')
  
  INSERT INTO Category (CategoryID,Name, Notes)
  VALUES (1,'Database Development', '-'),
  (2,'Business Intelligene','-');

  INSERT INTO Article (ArticleId,CategoryId, AuthorId, Title, Published, Notes)
  VALUES (1,1, 1, 'Advanced Database Development', DATEFROMPARTS(2017,02,01),'10K Views'),
  (1,1, 1, 'Database Development with Cloud Technologies', DATEFROMPARTS(2017,02,01),'5K Views'),
  (1,1, 1, 'Developing Databases with Modern Tools', DATEFROMPARTS(2017,03,01),'20K Views'),
  (1,2, 2, 'Business Intelligence Fundamentals', DATEFROMPARTS(2017,02,01),'10K Views'),
  (1,2, 2, 'Tabular Models', DATEFROMPARTS(2017,02,01),'50K Views')

  -- Create an expected table
  CREATE TABLE ArticlesPerAuthorReport.Expected
  (Author VARCHAR(40),[Total Articles] int)  

  -- Add expected results into an expected table
  INSERT INTO ArticlesPerAuthorReport.Expected (Author, [Total Articles])
  VALUES ('Zak', 3), ('Akeel',2)
END;
GO

Agora, remova o código de teste que escrevemos no procedimento de configuração do teste de unidade anterior para verificar ArticlesPerAuthorReport saídas da seguinte forma:
-- (11) Create unit test check ArticlesPerAuthorReport outputs correct
ALTER PROCEDURE ArticlesPerAuthorReport.[test to check ArticlesPerAuthorReport outputs correct]
AS
BEGIN
  --Assemble (Test Code written in Setup Procedure)
  -- Create mocked up tables (blank copies of original tables without constraints and data)
  -- Add rows to the mocked up tables
  -- Create an expected table
  -- Add expected results into an expected table
  
  --Act
  --  Run ArticlesPerAuthorReport object (view) and put results into an actual table
  SELECT * INTO ArticlesPerAuthorReport.Actual FROM ArticlesPerAuthorReport apar
  

  --Assert
  --  Compare the expected and actual tables
  EXEC TSQLT.AssertEqualsTable @Expected = N'ArticlesPerAuthorReport.Expected'
                              ,@Actual = N'ArticlesPerAuthorReport.Actual'
                              
END;
GO

Running All Unit Tests to Check Setup Procedure Working


Run the unit tests and see the results:

The unit tests have run successfully despite the fact we are using a setup procedure to run some parts of the test code before these unit tests are running.

Use of Stored Procedures


Next, we’ll focus on creating stored procedures through test-driven database development (TDDD) to meet specific requirements that cannot be fulfilled by using a database view.

Let’s assume that business users want to know the Total number of articles per author for a specified year . The database view can’t meet it because it is not defined at the time of writing the script (exactly the year is going to be desired by the business users).

Thus, it requires a database object with parameter(s) capability and it is exactly the stored procedure.

Let us consider a new business requirement to create the report that shows the total number of articles per author for a specified year . We’ll use the sample database called SQLDevBlogReportTDD that we created earlier.

Run the ArticlesPerAuthorReport view to see the results:

Select the Database Object (AuthorsPerArticleByYearReport)


Name the potential database object as AuthorsPerArticleForYearReport .

As we mentioned above, the database view can meet the reporting requirement despite the absence of the specified year . But this variable means that we need the stored procedure which will pass year as an argument to run the report and show the desired results.

Write and Run the Object Exists Unit Test


As we already know, we need to start with writing the basic unit test to check the existence or absence of the desired object.

To create the first database unit test, right-click the SQLDevBlogReport database> Unit Test > Add New Test

Write the following test code:
CREATE PROCEDURE ArticlesPerAuthorByYearReport.[test to check ArticlesPerAuthorByYearReport exists]

AS

BEGIN

--Assemble

--Act

--Assert

EXEC tSQLt.AssertObjectExists @ObjectName = N'ArticlesPerAuthorByYearReport'

,@Message = N''


END;

GO

Right-click on the database> click View Test List under Unit Test to see the Test List Manager :

Check the ArticlesPerAuthorByYearReport test class and click the run test icon:

This complies with TDDD – the unit test checking if object existence is written before the object is created. So, we expect the test to fail first.

Create Object Stub (dummy object)


We are going to create an object stub that mocks the object’s functionality. At this stage, we only need that object, the desired functionality is out of the question.

Create a stored procedure type object as the stub and call it ArticlesPerAuthorByYearReport by using the following code:
-- Create report object (stored procedure) stub

CREATE PROCEDURE dbo.ArticlesPerAuthorByYearReport

@Year INT

AS

SELECT 'Adil' AS Author, 10 AS [Total Articles], 0000 AS [Year]

UNION ALL

SELECT 'Sam' AS Author, 5 AS [Total Articles], 0000 AS [Year]

GO

After we created the object stub, the basic unit test that checks for the existence of the object will be successful:

Write and Run the Object Functionality Unit Test


To comply with TDDD, we need to write a unit test to check whether the desired object ArticlesPerAuthorByYearReport funciona corretamente. Since the object was created as a stub (placeholder), this unit test is also going to fail first. The object has to function properly yet despite the fact it was created and passed the basic check of its existence.

Create a second unit test to check if the object outputs correct data by creating a setup procedure (which helps us to write shared test code within the same test class) that is followed by the unit test:
CREATE PROCEDURE ArticlesPerAuthorByYearReport. Setup

AS

BEGIN

--Assemble

-- Create mocked up tables (blank copies of original tables without constraints and data)

EXEC tSQLt.FakeTable @TableName = N'Author'

,@SchemaName = N'dbo'




EXEC tSQLt.FakeTable @TableName = N'Article'

,@SchemaName = N'dbo'




EXEC tSQLt.FakeTable @TableName = N'Category'

,@SchemaName = N'dbo'




-- Add rows to the mocked up tables

INSERT INTO Author (AuthorId,Name, RegistrationDate, Notes)

VALUES (1,'Zak', DATEFROMPARTS(2017,01,01), 'Database Author'),

(2,'Akeel',DATEFROMPARTS(2018,01,01),'Business Intelligence Author')

INSERT INTO Category (CategoryID,Name, Notes)

VALUES (1,'Database Development', '-'),

(2,'Business Intelligene','-');




INSERT INTO Article (ArticleId,CategoryId, AuthorId, Title, Published, Notes)

VALUES (1,1, 1, 'Advanced Database Development', DATEFROMPARTS(2017,02,01),'10K Views'),

(1,1, 1, 'Database Development with Cloud Technologies', DATEFROMPARTS(2017,02,01),'5K Views'),

(1,1, 1, 'Developing Databases with Modern Tools', DATEFROMPARTS(2017,03,01),'20K Views'),

(1,2, 2, 'Business Intelligence Fundamentals', DATEFROMPARTS(2016,02,01),'10K Views'),

(1,2, 2, 'Tabular Models', DATEFROMPARTS(2016,02,01),'50K Views')




-- Create an expected table

CREATE TABLE ArticlesPerAuthorByYearReport.Expected

(Author VARCHAR(40),[Total Articles] INT,[Year] INT)




-- Create an actual table

CREATE TABLE ArticlesPerAuthorByYearReport.Actual

(Author VARCHAR(40),[Total Articles] INT,[Year] INT)




-- Add expected results into an expected table for the year 2017

INSERT INTO ArticlesPerAuthorByYearReport.Expected (Author, [Total Articles],[Year])

VALUES ('Zak', 3,2017)




END;

GO

Write the unit test to check if the object functions properly:
-- Create unit test to check ArticlesPerAuthorByYearReport outputs correct data

CREATE PROCEDURE ArticlesPerAuthorByYearReport.[test to check ArticlesPerAuthorByYearReport outputs correct data]

AS

BEGIN

--Assemble (Test Code written in Setup Procedure)

-- Create mocked up tables (blank copies of original tables without constraints and data)

-- Add rows to the mocked up tables

-- Create an expected table

-- Create an actual table

-- Add expected results into an expected table

--Act

-- Call desired object (stored procedure) and put results into an actual table

INSERT INTO ArticlesPerAuthorByYearReport.Actual

EXEC dbo.ArticlesPerAuthorByYearReport @Year=2017




--Assert

-- Compare the expected and actual tables

EXEC TSQLT.AssertEqualsTable @Expected = N'ArticlesPerAuthorByYearReport.Expected'

,@Actual = N'ArticlesPerAuthorByYearReport.Actual'

END;

GO

Run the unit test. As demonstrated earlier, it will fail first since we have not added the desired functionality to the object yet:

Add Object Functionality and Rerun the Unit Test


Add the object functionality by modifying the stored procedure as follows:
-- Create report object (stored procedure) to show articles per author for a specified year

CREATE PROCEDURE dbo.ArticlesPerAuthorByYearReport

@Year INT

AS




SELECT

a.Name AS [Author],COUNT(a1.ArticleId) AS [Total Articles],YEAR(a.RegistrationDate) AS [Year]

FROM Author a

INNER JOIN Article a1

ON a.AuthorId = a1.AuthorId

WHERE YEAR(a.RegistrationDate) = @Year

GROUP BY a.Name,YEAR(a.RegistrationDate)

GO

Note :If you are using a declarative database development tool like dbForge Studio for SQL Server, you’ll use the Create Procedure statement to modify the object. For tools like SSMS (SQL Server Management Studio), you must use ALTER Procedure .

Rerunning the database unit test for checking the proper object functioning gives us the following results:

You have successfully unit tested the reporting procedure that is responsible for meeting the business requirement.

Conclusão


Test-driven database development (TDDD) is a specific approach. To meet the business requirement(s), potential database object(s) must pass the unit test(s) and satisfy the following conditions under normal circumstances:
  • The database object must exist
  • The database object must function properly to meet the business requirement

First, the unit tests have to fail because they are created before the creation of the object/defining the object functionality. After adding the necessary objects and ensuring their functionality, the unit tests succeed.

This article examined the basics of test-driven database development and illustrated it with practical examples. We hope that the article was helpful to you. Feel free to share your opinions and maybe some lifehacks in the Comments section, and stay tuned for the next materials!