Alla databasutvecklare skriver mer eller mindre databasenhetstester som inte bara hjälper till att upptäcka buggar tidigt utan också sparar mycket tid och ansträngningar när det oväntade beteendet hos databasobjekt blir ett produktionsproblem.
Nuförtiden finns det ett antal ramverk för testning av databasenheter som tSQLt tillsammans med verktyg för enhetstestning från tredje part inklusive dbForge Unit Test.
Å ena sidan är fördelen med att använda testverktyg från tredje part att utvecklingsteamet omedelbart kan skapa och köra enhetstester med extra funktioner. Att använda ett testramverk ger dig också direkt mer kontroll över enhetstesterna. Därför kan du lägga till mer funktionalitet till själva ramverket för enhetstestning. Men i det här fallet måste ditt team ha tid och en viss expertis för att göra detta.
Den här artikeln utforskar några standardmetoder som kan hjälpa oss att förbättra hur vi skriver databasenhetstester.
Låt oss först gå igenom några nyckelbegrepp för testning av databasenheter.
Vad är Databas Unit Testing
Enligt Dave Green säkerställer databasenhetstester att små enheter i databasen, såsom tabeller, vyer, lagrade procedurer, etc., fungerar som förväntat.
Databasenhetstester skrivs för att verifiera om koden uppfyller affärskraven.
Om du till exempel får ett krav som "En bibliotekarie (slutanvändare) ska kunna lägga till nya böcker till biblioteket (Management Information System)", måste du tänka på att tillämpa enhetstester för den lagrade proceduren för att kontrollera om den kan lägga till en ny bok i boken bord.
Ibland säkerställer en serie enhetstester att koden uppfyller kraven. Därför tillåter de flesta ramverk för enhetstestning, inklusive tSQLt, gruppering av relaterade enhetstester i en enda testklass snarare än att köra individuella tester.
AAA-princip
Det är värt att nämna om 3-stegsprincipen för enhetstestning som är en standardpraxis för att skriva enhetstester. AAA-principen är grunden för enhetstestning och består av följande steg:
- Arrangera/montera
- Göra
- Förstå
Arrangera avsnittet är det första steget i att skriva databasenhetstester. Den vägleder genom att konfigurera ett databasobjekt för att testa och ställa in de förväntade resultaten.
Akten sektionen är när ett databasobjekt (under test) anropas för att producera den faktiska utdata.
Förstå Steget handlar om att matcha den faktiska produktionen till den förväntade och verifiera om testet antingen godkänns eller inte.
Låt oss utforska dessa metoder med särskilda exempel.
Om vi skapar ett enhetstest för att verifiera att AddProduct lagrad procedur kan lägga till en ny produkt, ställer vi in Produkten och ExpectedProduct tabeller efter att produkten har lagts till. I det här fallet hamnar metoden under Arrange/Assemble-sektionen.
Att anropa AddProduct-proceduren och lägga in resultatet i produkttabellen täcks av lagen.
Assert-delen matchar helt enkelt Produkttabellen med ExpectedProduct-tabellen för att se om den lagrade proceduren har körts framgångsrikt eller misslyckats.
Förstå beroenden i enhetstestning
Hittills har vi diskuterat grunderna för databasenhetstestning och vikten av AAA-principen (Assemble, Act, and Assert) när du skapar ett standardenhetstest.
Låt oss nu fokusera på en annan viktig pusselbit – beroenden i enhetstestning.
Förutom att följa AAA-principen och bara fokusera på ett visst databasobjekt (under test), behöver vi också känna till de beroenden som kan påverka enhetstester.
Det bästa sättet att förstå beroenden är att titta på ett exempel på ett enhetstest.
Anställda Exempel Databas Setup
För att gå vidare, skapa en exempeldatabas och kalla den EmployeesSample :
-- Create the Employees sample database to demonstrate unit testing CREATE DATABASE EmployeesSample; GO
Skapa nu Anställd tabell i exempeldatabasen:
-- Create the Employee table in the sample database USE EmployeesSample CREATE TABLE Employee (EmployeeId INT PRIMARY KEY IDENTITY(1,1), NAME VARCHAR(40), StartDate DATETIME2, Title VARCHAR(50) ); GO
Pulera provdata
Fyll tabellen genom att lägga till några poster:
-- Adding data to the Employee table INSERT INTO Employee (NAME, StartDate, Title) VALUES ('Sam','2018-01-01', 'Developer'), ('Asif','2017-12-12','Tester'), ('Andy','2016-10-01','Senior Developer'), ('Peter','2017-11-01','Infrastructure Engineer'), ('Sadaf','2015-01-01','Business Analyst'); GO
Tabellen ser ut så här:
-- View the Employee table SELECT e.EmployeeId ,e.NAME ,e.StartDate ,e.Title FROM Employee e; GO
Observera att jag använder dbForge Studio för SQL Server i den här artikeln. Således kan utdatautseendet skilja sig om du kör samma kod i SSMS (SQL Server Management Studio). Det är ingen skillnad när det kommer till skript och deras resultat.
Krav för att lägga till ny anställd
Nu, om ett krav på att lägga till en ny anställd har mottagits, är det bästa sättet att uppfylla kravet att skapa en lagrad procedur som framgångsrikt kan lägga till en ny anställd i tabellen.
För att göra detta, skapa AddEmployee-lagrade proceduren enligt följande:
-- Stored procedure to add a new employee CREATE PROCEDURE AddEmployee @Name VARCHAR(40), @StartDate DATETIME2, @Title VARCHAR(50) AS BEGIN SET NOCOUNT ON INSERT INTO Employee (NAME, StartDate, Title) VALUES (@Name, @StartDate, @Title); END
Enhetstest för att verifiera om kravet är uppfyllt
Vi kommer att skriva ett databasenhetstest för att verifiera om den lagrade proceduren AddEmployee uppfyller kravet att lägga till en ny post i Employee-tabellen.
Låt oss fokusera på att förstå enhetstestfilosofin genom att simulera en enhetstestkod snarare än att skriva ett enhetstest med ett testramverk eller ett enhetstestverktyg från tredje part.
Simulering av enhetstest och tillämpning av AAA-principen i SQL
Det första vi behöver göra är att imitera AAA-principen i SQL eftersom vi inte kommer att använda något ramverk för enhetstestning.
Monteringssektionen används när de faktiska och förväntade tabellerna normalt sätts upp tillsammans med den förväntade tabellen som fylls i. Vi kan använda SQL-variabler för att initiera den förväntade tabellen i detta steg.
Act-sektionen används när den faktiska lagrade proceduren anropas för att infoga data i själva tabellen.
Assert-sektionen är när den förväntade tabellen matchar den faktiska tabellen. Att simulera Assert-delen är lite knepigt och kan uppnås genom följande steg:
- Räknar de vanliga (matchande) raderna mellan två tabeller som ska vara 1 (eftersom den förväntade tabellen bara har en post som ska matcha den faktiska tabellen)
- Att exkludera de faktiska tabellposterna från de förväntade tabellposterna bör vara lika med 0 (om posten i den förväntade tabellen också finns i den faktiska tabellen, då bör exkludering av alla faktiska tabellposter från den förväntade tabellen returnera 0)
SQL-skriptet är som följer:
[expand title="Kod"]
-- Simulating unit test to test the AddEmployee stored procedure CREATE PROCEDURE TestAddEmployee AS BEGIN -- (1) Assemble -- Set up new employee data DECLARE @EmployeeId INT = 6 ,@NAME VARCHAR(40) = 'Adil' ,@StartDate DATETIME2 = '2018-03-01' ,@Title VARCHAR(50) = 'Development Manager' -- Set up the expected table CREATE TABLE #EmployeeExpected ( EmployeeId INT PRIMARY KEY IDENTITY (6, 1) -- the expected table EmployeeId should begin with 6 -- since the actual table has already got 5 records and -- the next EmployeeId in the actual table is 6 ,NAME VARCHAR(40) ,StartDate DATETIME2 ,Title VARCHAR(50) ); -- Add the expected table data INSERT INTO #EmployeeExpected (NAME, StartDate, Title) VALUES (@NAME, @StartDate, @Title); -- (2) Act -- Call AddEmployee to add new employee data to the Employee table INSERT INTO Employee EXEC AddEmployee @NAME ,@StartDate ,@Title -- (3) Assert -- Match the actual table with the expected table DECLARE @ActualAndExpectedTableCommonRecords INT = 0 -- we assume that expected and actual table records have nothing in common SET @ActualAndExpectedTableCommonRecords = (SELECT COUNT(*) FROM (SELECT e.EmployeeId ,e.NAME ,e.StartDate ,e.Title FROM Employee e INTERSECT SELECT ee.EmployeeId ,ee.NAME ,ee.StartDate ,ee.Title FROM #EmployeeExpected ee) AS A) DECLARE @ExpectedTableExcluldingActualTable INT = 1 -- we assume that expected table has records which do not exist in the actual table SET @ExpectedTableExcluldingActualTable = (SELECT COUNT(*) FROM (SELECT ee.EmployeeId ,ee.NAME ,ee.StartDate ,ee.Title FROM #EmployeeExpected ee EXCEPT SELECT e.EmployeeId ,e.NAME ,e.StartDate ,e.Title FROM Employee e) AS A) IF @ActualAndExpectedTableCommonRecords = 1 AND @ExpectedTableExcluldingActualTable = 0 PRINT '*** Test Passed! ***' ELSE PRINT '*** Test Failed! ***' END
[/expand]
Kör simulerat enhetstest
Efter att den lagrade proceduren har skapats, kör den med det simulerade enhetstestet:
-- Running simulated unit test to check the AddEmployee stored procedure EXEC TestAddEmployee
Utgången är som följer:
Grattis! Databasenhetstestet godkändes.
Identifiera problem i form av beroenden i enhetstest
Kan vi upptäcka något fel i enhetstestet vi skapade trots att det har skrivits och körts framgångsrikt?
Om vi tittar noga på enhetens testsetup (monteringsdelen), har den förväntade tabellen en onödig bindning med identitetskolumnen:
Innan vi skriver ett enhetstest har vi redan lagt till 5 poster i den faktiska (anställda) tabellen. Sålunda, vid testinställningen, börjar identitetskolumnen för den förväntade tabellen med 6. Detta betyder dock att vi alltid förväntar oss att 5 poster finns i den faktiska (Anställd) tabellen för att matcha den med den förväntade tabellen (#EmployeeExpected).
För att förstå hur detta kan påverka enhetstestet, låt oss ta en titt på den faktiska (anställda) tabellen nu:
Lägg till ytterligare en post i tabellen Employee:
-- Adding a new record to the Employee table INSERT INTO Employee (NAME, StartDate, Title) VALUES ('Mark', '2018-02-01', 'Developer');
Ta en titt på tabellen för anställda nu:
Ta bort EmployeeId 6 (Adil) så att enhetstestet kan köras mot sin egen version av EmployeeId 6 (Adil) snarare än den tidigare lagrade posten.
-- Deleting the previously created EmployeeId: 6 (Adil) record from the Employee table DELETE FROM Employee WHERE EmployeeId=6
Kör det simulerade enhetstestet och se resultaten:
-- Running the simulated unit test to check the AddEmployee stored procedure EXEC TestAddEmployee
Testet har misslyckats den här gången. Svaret finns i resultatuppsättningen för personaltabellen enligt nedan:
Medarbetar-ID-bindningen i enhetstestet som nämnts ovan fungerar inte när vi kör enhetstestet igen efter att ha lagt till en ny post och raderat den tidigare tillagda personalposten.
Det finns tre typer av beroenden i testet:
- Databeroende
- Nyckelbegränsningsberoende
- Identitetskolumnberoende
Databeroende
Först och främst beror detta enhetstest på data i databasen. Enligt Dave Green, när det kommer till enhetstestdatabasen, är själva data ett beroende.
Detta innebär att ditt databasenhetstest inte bör förlita sig på data i databasen. Till exempel bör ditt enhetstest innehålla de faktiska data som ska infogas i databasobjektet (tabellen) istället för att förlita sig på de data som redan finns i databasen som kan raderas eller ändras.
I vårt fall är det faktum att fem poster redan har infogats i den faktiska Employee-tabellen ett databeroende som måste förhindras eftersom vi inte bör bryta mot filosofin med enhetstest som säger att bara enheten i koden testas.
Med andra ord bör testdata inte förlita sig på faktiska data i databasen.
Nyckelbegränsningsberoende
Ett annat beroende är ett nyckelbegränsningsberoende vilket betyder att den primära nyckelkolumnen EmployeeId också är ett beroende. Det måste förhindras för att kunna skriva ett bra enhetstest. Ett separat enhetstest krävs dock för att testa en primärnyckelbegränsning.
Till exempel, för att testa AddEmployee-lagrade proceduren, bör Employee-tabellens primärnyckel tas bort så att ett objekt kan testas utan att behöva oroa sig för att bryta mot en primärnyckel.
Identitetskolumnberoende
Precis som en primärnyckelbegränsning är identitetskolumnen också ett beroende. Det finns alltså inget behov av att testa logiken för automatisk ökning av identitetskolumnen för AddEmployee-proceduren; det måste undvikas till varje pris.
Isolera beroenden i enhetstestning
Vi kan förhindra alla tre beroenden genom att tillfälligt ta bort begränsningarna från tabellen och sedan inte vara beroende av data i databasen för enhetstestet. Så här skrivs standardtesterna för databasenhet.
I det här fallet kan man fråga sig varifrån uppgifterna för tabellen Employee kom. Svaret är att tabellen fylls i med testdata som definierats i enhetstestet.
Ändra enhetstest lagrad procedur
Låt oss nu ta bort beroenden i vårt enhetstest:
[expand title="Kod"]
-- Simulating dependency free unit test to test the AddEmployee stored procedure ALTER PROCEDURE TestAddEmployee AS BEGIN -- (1) Assemble -- Set up new employee data DECLARE @NAME VARCHAR(40) = 'Adil' ,@StartDate DATETIME2 = '2018-03-01' ,@Title VARCHAR(50) = 'Development Manager' -- Set actual table DROP TABLE Employee -- drop table to remove dependencies CREATE TABLE Employee -- create a table without dependencies (PRIMARY KEY and IDENTITY(1,1)) ( EmployeeId INT DEFAULT(0) ,NAME VARCHAR(40) ,StartDate DATETIME2 ,Title VARCHAR(50) ) -- Set up the expected table without dependencies (PRIMARY KEY and IDENTITY(1,1) CREATE TABLE #EmployeeExpected ( EmployeeId INT DEFAULT(0) ,NAME VARCHAR(40) ,StartDate DATETIME2 ,Title VARCHAR(50) ) -- Add the expected table data INSERT INTO #EmployeeExpected (NAME, StartDate, Title) VALUES (@NAME, @StartDate, @Title) -- (2) Act -- Call AddEmployee to add new employee data to the Employee table EXEC AddEmployee @NAME ,@StartDate ,@Title -- (3) Assert -- Match the actual table with the expected table DECLARE @ActualAndExpectedTableCommonRecords INT = 0 -- we assume that the expected and actual table records have nothing in common SET @ActualAndExpectedTableCommonRecords = (SELECT COUNT(*) FROM (SELECT e.EmployeeId ,e.NAME ,e.StartDate ,e.Title FROM Employee e INTERSECT SELECT ee.EmployeeId ,ee.NAME ,ee.StartDate ,ee.Title FROM #EmployeeExpected ee) AS A) DECLARE @ExpectedTableExcluldingActualTable INT = 1 -- we assume that the expected table has records which donot exist in actual table SET @ExpectedTableExcluldingActualTable = (SELECT COUNT(*) FROM (SELECT ee.EmployeeId ,ee.NAME ,ee.StartDate ,ee.Title FROM #EmployeeExpected ee EXCEPT SELECT e.EmployeeId ,e.NAME ,e.StartDate ,e.Title FROM Employee e) AS A) IF @ActualAndExpectedTableCommonRecords = 1 AND @ExpectedTableExcluldingActualTable = 0 PRINT '*** Test Passed! ***' ELSE PRINT '*** Test Failed! ***' -- View the actual and expected tables before comparison SELECT e.EmployeeId ,e.NAME ,e.StartDate ,e.Title FROM Employee e SELECT ee.EmployeeId ,ee.NAME ,ee.StartDate ,ee.Title FROM #EmployeeExpected ee -- Reset the table (Put back constraints after the unit test) DROP TABLE Employee DROP TABLE #EmployeeExpected CREATE TABLE Employee ( EmployeeId INT PRIMARY KEY IDENTITY (1, 1) ,NAME VARCHAR(40) ,StartDate DATETIME2 ,Title VARCHAR(50) ); END
[/expand]
Kör beroendefritt simulerat enhetstest
Kör det simulerade enhetstestet för att se resultaten:
-- Running the dependency-free simulated unit test to check the AddEmployee stored procedure EXEC TestAddEmployee
Kör enhetstestet igen för att kontrollera AddEmployee-lagrade proceduren:
-- Running the dependency-free simulated unit test to check the AddEmployee stored procedure EXEC TestAddEmployee
Grattis! Beroenden från enhetstestet har tagits bort.
Nu, även om vi lägger till en ny post eller uppsättning nya poster i tabellen Employee, kommer det inte att påverka vårt enhetstest eftersom vi har tagit bort data och begränsningsberoenden från testet.
Skapa databasenhetstest med tSQLt
Nästa steg är att skapa ett riktigt databasenhetstest baserat på det simulerade enhetstestet.
Om du använder SSMS (SQL Server Management Studio) måste du installera tSQLt-ramverket, skapa en testklass och aktivera CLR innan du skriver och kör enhetstestet.
Om du använder dbForge Studio för SQL Server kan du skapa enhetstestet genom att högerklicka på den lagrade proceduren AddEmployee och sedan klicka på "Unit Test" => "Add New Test..." som visas nedan:
För att lägga till ett nytt test, fyll i nödvändig enhetstestinformation:
För att skriva enhetstestet, använd följande skript:
-- Comments here are associated with the test. -- For test case examples, see: http://tsqlt.org/user-guide/tsqlt-tutorial/ CREATE PROCEDURE [BasicTests].[test if new employee can be added] AS BEGIN --Assemble DECLARE @NAME VARCHAR(40) = 'Adil' ,@StartDate DATETIME2 = '2018-03-01' ,@Title VARCHAR(50) = 'Development Manager' EXEC tSQLt.FakeTable "dbo.Employee" -- This will create a dependency-free copy of the Employee table CREATE TABLE BasicTests.Expected -- Create the expected table ( EmployeeId INT ,NAME VARCHAR(40) ,StartDate DATETIME2 ,Title VARCHAR(50) ) -- Add the expected table data INSERT INTO BasicTests.Expected (NAME, StartDate, Title) VALUES (@NAME, @StartDate, @Title) --Act EXEC AddEmployee @Name -- Insert data into the Employee table ,@StartDate ,@Title --Assert EXEC tSQLt.AssertEqualsTable @Expected = N'BasicTests.Expected' ,@Actual = N'dbo.Employee' ,@Message = N'Actual table matched with expected table' ,@FailMsg = N'Actual table does not match with expected table' END; GO
Kör sedan databasenhetstestet:
Grattis! Vi har framgångsrikt skapat och kört databasenhetstest som är fritt från beroenden.
Saker att göra
Det är allt. Du är redo att isolera beroenden från databasenhetstester och skapa databasenhetstest fria från data och begränsningsberoenden efter att ha gått igenom den här artikeln. Som ett resultat kan du förbättra dina färdigheter genom att utföra följande saker:
- Försök att lägga till den lagrade proceduren för Ta bort anställd och skapa ett simulerat databasenhetstest för Ta bort anställd med beroenden för att se om det misslyckas under vissa förhållanden
- Försök att lägga till den lagrade proceduren Ta bort anställd och skapa ett databasenhetstest fritt från beroenden för att se om en anställd kan tas bort
- Försök att lägga till Search Employee-lagrade proceduren och skapa ett simulerat databasenhetstest med beroenden för att se om en anställd kan sökas efter
- Försök att lägga till Search Employee-lagrade proceduren och skapa ett databasenhetstest fritt från beroenden för att se om en anställd kan sökas efter
- Försök mer komplexa krav genom att skapa lagrade procedurer för att uppfylla kraven och sedan skriva databasenhetstester fria från beroenden för att se om de klarar testet eller misslyckas. Se dock till att testet är repeterbart och fokuserat på att testa enheten för koden
Användbart verktyg:
dbForge Unit Test – ett intuitivt och bekvämt gränssnitt för att implementera automatiserad enhetstestning i SQL Server Management Studio.