sql >> Database >  >> RDS >> Database

Ga naar testgestuurde databaseontwikkeling (TDDD)

In de regel beginnen we met het ontwikkelen van database-oplossingen door database-objecten te maken, zoals tabellen, views, opgeslagen procedures, enz., op basis van zakelijke vereisten. Deze aanpak staat ook bekend als Conventionele database-ontwikkeling . In dit artikel gaan we deze aanpak onderzoeken en illustreren met voorbeelden.

Conventionele database-ontwikkeling

De ontwikkelstijl bestaat uit de volgende stappen:

  1. Ontvang de vereisten
  2. Maak database-objecten op basis van vereisten
  3. Voer eenheidstests uit voor database-objecten om te zien of ze aan de vereisten voldoen
  4. Ontvang nieuwe vereisten
  5. Bestaande database-objecten wijzigen of nieuwe toevoegen om aan de nieuwe vereisten te voldoen
  6. Maak eenheidstests en voer ze uit om te controleren of nieuwe vereisten dienovereenkomstig werken en niet in strijd zijn met de vorige

Laten we, om de processen te verkennen en te illustreren, beginnen met het opzetten van een voorbeelddatabase SQLDevBlog :

 
-- Create sample database (SQLDevBlog)
  CREATE DATABASE SQLDevBlog

Gebruik de volgende code om tabellen in die voorbeelddatabase te maken:

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

Bekijk het databasediagram met onze nieuw gemaakte tabellen:

Opmerking :Ik gebruik hier dbForge Studio voor SQL Server om alle taken uit te voeren. Het uiterlijk van de uitvoer kan verschillen van SSMS (SQL Server Management Studio), maar de resultaten zijn hetzelfde.

Vervolgens vullen we onze SQLDevBlog voorbeelddatabase om een ​​realistischer scenario te creëren:

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

Als resultaat hebben we de volgende gevulde tabellen:

Nu we klaar zijn met het instellen van de database en het vullen van tabellen, staan ​​we voor de volgende stap. We moeten het scenario nabootsen met een nieuwe eis.

De vereiste om een ​​nieuwe categorie toe te voegen

Een nieuwe vereiste stelt dat een beheerder in staat moet zijn om een nieuwe categorie toe te voegen aan de lijst met beschikbare categorieën . Om aan deze vereiste te voldoen, moet uw ontwikkelteam een ​​opgeslagen procedure bedenken om gemakkelijk een nieuwe vereiste toe te voegen. Of we moeten een AddCategory . maken Database-object.

Voer het volgende script uit om de opgeslagen procedure te maken:

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

Het resultaat is als volgt:

Maak een database-eenheidtest om te controleren of de procedure goed werkt

De volgende stap is het maken van de database-eenheidstest om te controleren of de opgeslagen procedure aan de specificatie voldoet.

Deze tip werkt voor dbForge Studio voor SQL Server (of alleen dbForge-eenheidstest ) en SSMS (SQL Server Management Studio) . Opmerking:als u SSMS (SQL Server Management Studio) gebruikt, moet u de tSQLt installeren Kader om de unit-tests te schrijven.

Om de eerste database-eenheidstest te maken, klikt u met de rechtermuisknop op de SQLDevBlog database> Eenheidstest > Nieuwe test toevoegen

De Nieuwe test toevoegen raam opent. Vul alle vereiste informatie in en klik op Test toevoegen .

Maak de eenheidstest als volgt aan en sla deze op:

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

Klik op de Database menu> Eenheidstest > Bekijk testlijst en voer de eenheidstest uit zoals hieronder weergegeven:

We kunnen zien dat de unit-test succesvol is. Zo kan er een nieuwe categorie aan de database (tabel) worden toegevoegd. Aan de eis is voldaan.

Nu gaan we testgestuurde databaseontwikkeling onderzoeken en beschrijven hoe het proces van het schrijven van unittests aan de vereisten kan voldoen.

Testgestuurde databaseontwikkeling (TDDD)

Testgestuurde databaseontwikkeling (TDDD) begint met het schrijven van de unittest die als eerste zal mislukken. Daarna passen we het aan om het te laten slagen en verfijnen we het.

Unit-tests zijn geschreven om te voldoen aan de vereisten en unit-tests die vereisen dat database-objecten worden gemaakt en correct worden uitgevoerd.

Om het verschil tussen traditionele databaseontwikkeling en testgestuurde databaseontwikkeling te begrijpen, maken we de SQLDevBlogTDD databank. Het is hetzelfde als SQLDevBlog .

-- Create sample database (SQLDevBlogTDD)
  CREATE DATABASE SQLDevBlogTDD

Vul vervolgens de voorbeelddatabase met tabellen:

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

We moeten onze voorbeelddatabase als volgt vullen om een ​​realistischer scenario te creëren:

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

Vereiste om nieuwe categorie toe te voegen (TDDD)

Nu hebben we dezelfde eis:de beheerder moet een nieuwe categorie kunnen toevoegen aan de lijst met beschikbare categorieën. Om aan de vereiste te voldoen, moeten we eerst een database-eenheidstest schrijven die naar een potentieel object zoekt.

Dat is hoe TDDD werkt:de unittest mislukt eerst omdat we aannemen dat we op zoek zijn naar het object dat momenteel niet bestaat, maar het er binnenkort zal zijn.

Maak de database-eenheidtest en voer deze uit om te controleren of het gewenste object bestaat

Hoewel we weten dat het nu niet bestaat, beschouwen we dit als een startpunt.

In dbForge Studio voor SQL Server wordt de test van de database-eenheid die standaard TDDD ondersteunt, gemaakt om eerst te mislukken. Dan gaan we het een beetje veranderen. Als u een tSQLt . gebruikt database unit test framework direct, schrijf de volgende unit test:

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

Nadat u de test van de database-eenheid hebt uitgevoerd, kunt u zien dat de test mislukt:

Maak het databaseobject en voer de eenheidstest opnieuw uit

De volgende stap is het maken van het vereiste databaseobject. In ons geval is het een opgeslagen procedure.

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

Voer de eenheidstest opnieuw uit – deze keer is het gelukt:

Maar het is niet voldoende om de eenheidstest te doorstaan ​​om te controleren of de opgeslagen procedure bestaat. We moeten ook controleren of de opgeslagen procedure een nieuwe categorie toevoegt.

Maak de database-eenheidtest om te controleren of routine goed werkt

Laten we de nieuwe test voor database-eenheden maken:

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

Zoals je kunt zien, mislukt de unit-test voor de eerste keer en slaagt voor de tweede keer:

Functie toevoegen aan de routine- en herhaaleenheidstest

Wijzig de opgeslagen procedure door de vereiste functionaliteit toe te voegen zodat de test kan slagen, zoals hieronder weergegeven:

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

Voer de unit-tests opnieuw uit om te controleren of ze allemaal slagen, inclusief de recent gewijzigde opgeslagen procedure:

Op deze manier hebben we met succes testgestuurde database-ontwikkeling geïmplementeerd. Nu kunnen we ons alleen concentreren op de vereisten. De unit-tests kapselen de vereisten in en eisen dat database-objecten worden gemaakt en correct worden uitgevoerd om aan de specificatie te voldoen.

Laten we eens kijken hoe effectief TDDD is als het gaat om het voldoen aan een zakelijke rapportagevereiste.

Voldoende aan de vereisten voor bedrijfsrapportage via TDDD

We gaan ervan uit dat de database al de benodigde objecten (zoals tabellen) heeft gekregen voordat de nieuwe zakelijke rapportagevereiste werd ontvangen.

Laten we een voorbeelddatabase maken met de naam SQLDevBlogReportTDD :

-- Create sample database (SQLDevBlogReportTDD)
CREATE DATABASE SQLDevBlogReportTDD;
GO

Maak en vul vervolgens de tabellen voor de voorbeelddatabase met behulp van de volgende code:

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

Maak een weergave om de lijst met alle auteurs, artikelen en artikelcategorieën te zien:

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

Voer de weergave van de gemaakte tabel uit om de resultaten te zien:

Na het verwerken van de databaseconfiguratie en het vullen van tabellen, is de volgende stap het scenario na te bootsen waarin we een nieuwe vereiste hebben ontvangen.

Zakelijke vereiste:totaal aantal artikelen per auteursrapport

Overweeg een nieuwe zakelijke rapportagevereiste. Het moet een databaserapport vermelden om het totale aantal artikelen per auteur te kunnen zien.

Het eerste is om een ​​databaseobject toe te wijzen dat aan de zakelijke vereisten kan voldoen. In ons geval is dit de ArticlesPerAuthorReport database-object.

Om aan de vereiste te voldoen, is het noodzakelijk om een ​​database-eenheidstest te maken die zoekt naar een mogelijk geschikt object. Zoals we weten, zal deze test het eerst mislukken omdat er wordt gezocht naar het object dat op dit moment niet bestaat, maar er binnenkort wel zal zijn.

TDDD-implementatieplan

Volgens de normen van testgestuurde testen van database-eenheden, moeten de volgende dingen aanwezig zijn om aan de rapportagevereiste te voldoen:

  1. Ontwikkel een enkel database-object dat voldoet aan de rapportage-eis.
  2. Maak een eenheidstest om het bestaan ​​van het object te controleren.
  3. Maak een objectstub (tijdelijke aanduiding) om de eerste test te doorstaan.
  4. Maak een tweede eenheidstest om te controleren of het object de juiste gegevens in de tabel invoert met de juiste invoer.
  5. Wijzig de objectdefinitie om de tweede test te laten slagen.

Maak de database-eenheidtest om te controleren of het gewenste object bestaat

We wijzen een database-object toe dat aan de zakelijke vereisten kan voldoen. In ons geval is dit de ArticlesPerAuthorReport database-object. De volgende stap is het maken van een test voor database-eenheden.

Klik met de rechtermuisknop op het SQLDevBlogReport om de eerste database-eenheidstest te maken. database> Eenheidstest > Nieuwe test toevoegen

Schrijf de volgende code om de eenheidstest te maken die het bestaan ​​of de afwezigheid van het gewenste object controleert:

CREATE PROCEDURE ArticlesPerAuthorReport.[test to check ArticlesPerAuthorReport exists]
AS
BEGIN
  --Assemble
 
  --Act
  
  --Assert
   EXEC tSQLt.AssertObjectExists @ObjectName = N'ArticlesPerAuthorReport'
END;
GO

De eenheidstest moet mislukken, omdat wordt gecontroleerd op het eerder gemaakte object. Het object zelf is gemaakt om te voldoen aan TDDD:

Maak Object Stub en voer de eenheid uit

Maak een objectstub met wat hardgecodeerde verwachte uitvoer, omdat we gewoon een database-object willen maken met het verwachte resultaat. Maak het ArticlesPerAuthorReport object eerst als een weergavestub (tijdelijke aanduiding):

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

Het uitvoeren van de eenheidstest zou succesvol moeten zijn:

Het maken van een stub dient als een kickstarter voor TDDD. We maken het object om de test te doorstaan ​​en maken ons geen zorgen over het daadwerkelijke functioneren van het object.

Maak de eenheidstest en voer deze uit om te controleren of het object correcte gegevens uitvoert

Het is tijd om te controleren of het gewenste object goed functioneert. Maak nog een eenheidstest om te controleren op de gegevensuitvoer door het gewenste object (ArticlesPerAuthorReport ). Laten we een nieuwe eenheidstest aan de database toevoegen:

Voeg de volgende eenheidstestcode toe:

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

Voer de eenheidstest uit die ook moet falen om te voldoen aan TDDD:

Voeg vereiste functionaliteit toe aan het ArticlesPerAuthorReport-object

De eenheidstest die de functionaliteit van het object controleert, vereist een aangepaste structuur zodat de test kan slagen.

Wijzig het ArticlesPerAuthorReport weergave om de tweede unit-test net als de eerste te laten slagen:

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

Het databaseobject is met succes gewijzigd om de gewenste gegevens uit te voeren. Voer alle eenheidstests uit:

De ArticlesPerAuthorReport object is klaar.

Onze volgende taak is om een ​​overzicht te geven van het maken van een rapportbasis op een databaseobject dat is ontwikkeld en getest met behulp van testgestuurde ontwikkeling (TDDD).

Implementatie van rapportagevereiste (ArticlesPerAuthorReport)

Eerst gaan we de SQLDevBlogReportTDD . resetten en voeg er meer gegevens aan toe. Of u kunt voor de eerste keer een lege database maken.

Om voldoende gegevens aan onze voorbeelddatabase toe te voegen, haalt u gegevens op uit de vwAuthors om alle records te zien:

De eenheidstests uitvoeren

Voer de database-eenheidtests uit die we eerder hebben gemaakt:

Gefeliciteerd, beide tests zijn geslaagd, wat betekent dat het gewenste database-object in staat is om te voldoen aan de rapportage-eis om artikelen per auteur te bekijken.

Maak databaserapport op basis van ArticlesPerAuthorsReport Object

U kunt op vele manieren een databaserapport maken (zoals met Report Builder, Report Server Project maken in Visual Studio Data Tools of dbForge Studio voor SQL Server gebruiken).

In deze sectie gebruiken we dbForge Studio voor SQL Server voor het maken van rapporten. Klik om door te gaan op Nieuw uit het Bestand Menu> Gegevensrapport :

Klik op Standaardrapport :

Selecteer Eenvoudige tabel\Weergave als Gegevenstype :

Voeg het basisobject toe (ArticlesPerAuthorReport ), wat in ons geval een weergave is:

Voeg de vereiste velden toe:

We hebben op dit moment geen groepering nodig, dus ga verder door te klikken op Volgende :

Selecteer Indeling en Oriëntatie van het gegevensrapport:

Voeg ten slotte titel toe Artikelen per auteurrapport en klik op Voltooien :

Pas vervolgens de opmaak van het rapport aan volgens de vereisten:

Klik op Voorbeeld om het databaserapport te zien:

Sla het rapport op als ArticlesPerAuthorReport . Het databaserapport is gemaakt vanwege de zakelijke vereisten.

Gebruik van de installatieprocedure

Wanneer u tests voor database-eenheden schrijft met tSQLt, zult u zien dat bepaalde testcode vaak wordt herhaald. U kunt het dus definiëren in een instellingsprocedure en het daarna opnieuw gebruiken in andere eenheidstests van die specifieke testklasse. Elke testklasse kan slechts één instellingsprocedure hebben die automatisch wordt uitgevoerd voordat de unittests van die klasse worden verwerkt.

We kunnen bijna alle testcode die is geschreven onder Assembleer . plaatsen (sectie) onder de installatieprocedure om codeduplicatie te voorkomen.

We moeten bijvoorbeeld bespotte tabellen maken samen met een verwachte tabel. Vervolgens voegen we gegevens toe aan gesimuleerde tabellen en vervolgens aan de verwachte tabel. We kunnen het gemakkelijk definiëren onder de instellingsprocedure en het verder hergebruiken.

Configuratieprocedure maken om duplicatie van testcodes te voorkomen

Maak een opgeslagen procedure in SQLDevBlogTDD als volgt:

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

Verwijder nu de testcode die we hebben geschreven in de installatieprocedure van de vorige eenheidstest om ArticlesPerAuthorReport te controleren voert als volgt uit:

-- (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 functions properly. 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

Opmerking :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.

Conclusie

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!


  1. Het Hadoop Input Output System begrijpen

  2. Zoek de associaties tussen database-e-mailaccounts en database-principals in SQL Server (T-SQL)

  3. Hoe de JDK-versie in Oracle te controleren?

  4. Semantisch zoeken gebruiken in SQL Server