sql >> Database teknologi >  >> RDS >> Database

Kunsten at isolere afhængigheder og data i databaseenhedstestning

Alle databaseudviklere skriver mere eller mindre databaseenhedstest, der ikke kun hjælper med at opdage fejl tidligt, men også sparer en masse tid og kræfter, når databaseobjekternes uventede adfærd bliver et produktionsproblem.

I dag er der en række databaseenhedstestrammer såsom tSQLt sammen med tredjeparts enhedstestværktøjer, herunder dbForge Unit Test.

På den ene side er fordelen ved at bruge tredjeparts testværktøjer, at udviklingsteamet øjeblikkeligt kan oprette og køre enhedstests med tilføjede funktioner. Brug af en testramme giver dig også direkte mere kontrol over enhedstestene. Derfor kan du tilføje mere funktionalitet til selve enhedstestrammerne. Men i dette tilfælde skal dit team have tid og et vist niveau af ekspertise til at gøre dette.

Denne artikel udforsker nogle standardpraksis, der kan hjælpe os med at forbedre den måde, vi skriver databaseenhedstest på.

Lad os først gennemgå nogle nøglebegreber for test af databaseenheder.

Hvad er Database Unit Testing

Ifølge Dave Green sikrer databaseenhedstest, at små enheder i databasen, såsom tabeller, visninger, lagrede procedurer osv., fungerer som forventet.

Databaseenhedstests er skrevet for at verificere, om koden opfylder forretningskrav.

Hvis du f.eks. modtager et krav som "En bibliotekar (slutbruger) skal være i stand til at tilføje nye bøger til biblioteket (Management Information System)", skal du tænke på at anvende enhedstest til den lagrede procedure for at kontrollere, om den kan tilføje en ny bog til bogen tabel.

Nogle gange sikrer en række enhedstests, at koden opfylder kravene. Derfor tillader de fleste enhedstestrammeværker inklusive tSQLt gruppering af relaterede enhedstests i en enkelt testklasse i stedet for at køre individuelle tests.

AAA-princippet

Det er værd at nævne om 3-trins princippet for enhedstest, som er en standard praksis for at skrive enhedstests. AAA-princippet er grundlaget for enhedstestning og består af følgende trin:

  1. Arranger/saml
  2. Gør
  3. Bekræftelse

Arranger sektionen er det første trin i at skrive databaseenhedstests. Den guider gennem konfiguration af et databaseobjekt til test og opsætning af de forventede resultater.

loven sektion er, når et databaseobjekt (under test) kaldes for at producere det faktiske output.

Bekræftelse trin beskæftiger sig med at matche det faktiske output til det forventede og verificerer, om testen enten består eller ikke består.

Lad os udforske disse metoder på bestemte eksempler.

Hvis vi opretter en enhedstest for at bekræfte, at AddProduct gemt procedure kan tilføje et nyt produkt, opsætter vi Produktet og ExpectedProduct tabeller efter produktet er tilføjet. I dette tilfælde kommer metoden under sektionen Arranger/saml.

Kaldning af AddProduct-proceduren og indsættelse af resultatet i produkttabellen er omfattet af lovafsnittet.

Assert-delen matcher simpelthen produkttabellen med ExpectedProduct-tabellen for at se, om den lagrede procedure er blevet udført med succes eller mislykkedes.

Forståelse af afhængigheder i enhedstestning

Indtil videre har vi diskuteret det grundlæggende i databaseenhedstestning og vigtigheden af ​​AAA-princippet (Assemble, Act, and Assert), når der oprettes en standardenhedstest.

Lad os nu fokusere på en anden vigtig brik i puslespillet – afhængigheder i enhedstestning.

Udover at følge AAA-princippet og kun fokusere på et bestemt databaseobjekt (under test), skal vi også kende de afhængigheder, der kan påvirke enhedstests.

Den bedste måde at forstå afhængigheder på er at se på et eksempel på en enhedstest.

EmployeesSample Database Setup

For at komme videre skal du oprette en eksempeldatabase og kalde den EmployeesSample :

-- Create the Employees sample database to demonstrate unit testing

CREATE DATABASE EmployeesSample;
GO

Opret nu medarbejderen tabel i eksempeldatabasen:

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

Populering af prøvedata

Udfyld tabellen ved at tilføje nogle få 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 således ud:

-- View the Employee table

  SELECT e.EmployeeId
        ,e.NAME
        ,e.StartDate
        ,e.Title FROM  Employee e;
GO

Bemærk venligst, at jeg bruger dbForge Studio til SQL Server i denne artikel. Således kan output-looket variere, hvis du kører den samme kode i SSMS (SQL Server Management Studio). Der er ingen forskel, når det kommer til scripts og deres resultater.

Krav for at tilføje ny medarbejder

Nu, hvis et krav om at tilføje en ny medarbejder er blevet modtaget, er den bedste måde at opfylde kravet på at oprette en lagret procedure, som med succes kan tilføje en ny medarbejder til tabellen.

For at gøre dette skal du oprette AddEmployee-lagrede procedure som følger:

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

Enhedstest for at bekræfte, om kravet er opfyldt

Vi skal skrive en databaseenhedstest for at verificere, om den lagrede procedure for AddEmployee opfylder kravet om at tilføje en ny post til Employee-tabellen.

Lad os fokusere på at forstå enhedstestfilosofien ved at simulere en enhedstestkode i stedet for at skrive en enhedstest med en testramme eller et tredjeparts enhedstestværktøj.

Simulering af enhedstest og anvendelse af AAA-princippet i SQL

Den første ting, vi skal gøre, er at efterligne AAA-princippet i SQL, da vi ikke kommer til at bruge nogen enhedstestramme.

Samling-sektionen anvendes, når de faktiske og forventede tabeller normalt er sat op sammen med den forventede tabel, der bliver udfyldt. Vi kan gøre brug af SQL-variabler til at initialisere den forventede tabel i dette trin.

Act-sektionen bruges, når den faktiske lagrede procedure kaldes for at indsætte data i den aktuelle tabel.

Assert-sektionen er, når den forventede tabel matcher den faktiske tabel. Simulering af Assert-delen er en smule vanskelig og kan opnås ved følgende trin:

  • Tæller de fælles (matchende) rækker mellem to tabeller, som skal være 1 (da den forventede tabel kun har én post, der skal matche den faktiske tabel)
  • Ekskludering af de faktiske tabelposter fra de forventede tabelposter skal være lig med 0 (hvis posten i den forventede tabel også findes i den faktiske tabel, så skal ekskludering af alle de faktiske tabelposter fra den forventede tabel returnere 0)

SQL-scriptet er som følger:

[expand title="Kode"]

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

[/udvid]

Kører simuleret enhedstest

Når den lagrede procedure er oprettet, udføres den med den simulerede enhedstest:

-- Running simulated unit test to check the AddEmployee stored procedure
EXEC TestAddEmployee

Udgangen er som følger:

Tillykke! Databasenhedstesten bestået.

Identifikation af problemer i form af afhængigheder i enhedstest

Kan vi opdage noget forkert i den enhedstest, vi oprettede på trods af, at den er blevet skrevet og kørt med succes?

Hvis vi ser nærmere på enhedstestopsætningen (Assemble-delen), har den forventede tabel en unødvendig binding med identitetskolonnen:

Før vi skriver en enhedstest, har vi allerede tilføjet 5 poster til den faktiske (medarbejder) tabel. Ved testopsætningen begynder identitetskolonnen for den forventede tabel således med 6. Det betyder dog, at vi altid forventer, at 5 poster er i den faktiske (Medarbejder) tabel for at matche den med den forventede tabel (#EmployeeExpected).

For at forstå, hvordan dette kan påvirke enhedstesten, lad os nu se på den faktiske (medarbejder) tabel:

Tilføj endnu en post til medarbejdertabellen:

-- Adding a new record to the Employee table

INSERT INTO Employee (NAME, StartDate, Title)
  VALUES ('Mark', '2018-02-01', 'Developer');

Tag et kig på medarbejdertabellen nu:

Slet EmployeeId 6 (Adil), så enhedstesten kan køre mod sin egen version af EmployeeId 6 (Adil) i stedet for den tidligere gemte post.

-- Deleting the previously created EmployeeId: 6 (Adil) record from the Employee table

DELETE FROM Employee
  WHERE EmployeeId=6

Kør den simulerede enhedstest og se resultaterne:

-- Running the simulated unit test to check the AddEmployee stored procedure

EXEC TestAddEmployee

Testen mislykkedes denne gang. Svaret ligger i medarbejdertabellens resultatsæt som vist nedenfor:

Medarbejder-id-bindingen i enhedstesten som nævnt ovenfor fungerer ikke, når vi kører enhedstesten igen efter tilføjelse af en ny post og sletning af den tidligere tilføjede medarbejderpost.

Der er tre typer afhængigheder i testen:

  1. Dataafhængighed
  2. Nøglebegrænsningsafhængighed
  3. Identitetskolonneafhængighed

Dataafhængighed

Først og fremmest afhænger denne enhedstest af data i databasen. Ifølge Dave Green, når det kommer til enhedstestdatabasen, er selve dataene en afhængighed.

Det betyder, at din databaseenhedstest ikke bør stole på dataene i databasen. For eksempel bør din enhedstest indeholde de faktiske data, der skal indsættes i databaseobjektet (tabel), i stedet for at stole på de data, der allerede findes i databasen, som kan slettes eller ændres.

I vores tilfælde er det faktum, at der allerede er indsat fem poster i den faktiske medarbejder-tabel, en dataafhængighed, som skal forhindres, fordi vi ikke bør overtræde filosofien om enhedstest, der siger, at kun kodens enhed testes.

Med andre ord bør testdata ikke stole på de faktiske data i databasen.

Nøglebegrænsningsafhængighed

En anden afhængighed er en nøglebegrænsningsafhængighed, hvilket betyder, at den primære nøglekolonne EmployeeId også er en afhængighed. Det skal forhindres for at kunne skrive en god enhedstest. Der kræves dog en separat enhedstest for at teste en primær nøglebegrænsning.

For f.eks. at teste den lagrede procedure for AddEmployee, bør den primære nøgle til Employee-tabellen fjernes, så et objekt kan testes uden at bekymre dig om at krænke en primær nøgle.

Identitetskolonneafhængighed

Ligesom en primær nøglebegrænsning er identitetskolonnen også en afhængighed. Der er således ikke behov for at teste identitetskolonnens auto-increment-logik for AddEmployee-proceduren; det skal undgås for enhver pris.

Isolering af afhængigheder i enhedstestning

Vi kan forhindre alle tre afhængigheder ved at fjerne begrænsningerne fra tabellen midlertidigt og derefter ikke afhænge af dataene i databasen til enhedstesten. Sådan skrives standarddatabaseenhedstestene.

I dette tilfælde kan man spørge, hvor dataene til medarbejdertabellen kom fra. Svaret er, at tabellen bliver udfyldt med testdata defineret i enhedstesten.

Ændring af enhedstest lagret procedure

Lad os nu fjerne afhængighederne i vores enhedstest:

[expand title="Kode"]

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

[/udvid]

Kørsel af afhængighedsfri simuleret enhedstest

Kør den simulerede enhedstest for at se resultaterne:

-- Running the dependency-free simulated unit test to check the AddEmployee stored procedure

EXEC TestAddEmployee

Kør enhedstesten igen for at kontrollere AddEmployee-lagrede procedure:

-- Running the dependency-free simulated unit test to check the AddEmployee stored procedure

EXEC TestAddEmployee

Tillykke! Afhængigheder fra enhedstesten er blevet fjernet.

Nu, selvom vi tilføjer en ny post eller et sæt af nye poster til Employee-tabellen, vil det ikke påvirke vores enhedstest, da vi har fjernet data- og begrænsningsafhængighederne fra testen med succes.

Oprettelse af databaseenhedstest ved hjælp af tSQLt

Det næste trin er at oprette en ægte databaseenhedstest baseret på den simulerede enhedstest.

Hvis du bruger SSMS (SQL Server Management Studio), skal du installere tSQLt-rammeværket, oprette en testklasse og aktivere CLR, før du skriver og kører enhedstesten.

Hvis du bruger dbForge Studio til SQL Server, kan du oprette enhedstesten ved at højreklikke på AddEmployee stored procedure og derefter klikke på "Unit Test" => "Add New Test..." som vist nedenfor:

For at tilføje en ny test skal du udfylde de påkrævede enhedstestoplysninger:

For at skrive enhedstesten skal du bruge følgende script:

--  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 derefter databaseenhedstesten:

Tillykke! Vi har med succes oprettet og kørt databaseenhedstest, som er fri for afhængigheder.

Ting at gøre

Det er det. Du er klar til at isolere afhængigheder fra databaseenhedstest og oprette databaseenhedstest fri for data- og begrænsningsafhængigheder efter at have gennemgået denne artikel. Som et resultat kan du forbedre dine færdigheder ved at udføre følgende ting:

  1. Prøv venligst at tilføje Slet medarbejders lagrede procedure og opret en simuleret databaseenhedstest for Slet medarbejder med afhængigheder for at se, om den mislykkes under visse betingelser
  2. Prøv venligst at tilføje den lagrede procedure for Slet medarbejder og opret en databaseenhedstest fri for afhængigheder for at se, om en medarbejder kan slettes
  3. Prøv venligst at tilføje Search Employee-lagrede procedure og opret en simuleret databaseenhedstest med afhængigheder for at se, om der kan søges efter en medarbejder
  4. Prøv venligst at tilføje Search Employee-lagrede procedure og opret en databaseenhedstest fri for afhængigheder for at se, om der kan søges efter en medarbejder
  5. Prøv venligst mere komplekse krav ved at oprette lagrede procedurer for at opfylde kravene og derefter skrive databaseenhedstests fri for afhængigheder for at se, om de består testen eller fejler. Sørg dog for, at testen kan gentages og fokuserer på at teste kodens enhed

Nyttigt værktøj:

dbForge Unit Test – en intuitiv og praktisk GUI til implementering af automatiseret enhedstest i SQL Server Management Studio.


  1. Hvordan kan jeg fremskynde rækkenummer i Oracle?

  2. Sådan bruges BETWEEN-operatøren i SQL Server

  3. sql gruppe efter versus distinkt

  4. Indsæt array i MySQL-database med PHP