Všichni vývojáři databází víceméně píší testy databázových jednotek, které nejen pomáhají včas odhalit chyby, ale také ušetří spoustu času a úsilí, když se neočekávané chování databázových objektů stane produkčním problémem.
V současné době existuje řada rámců pro testování databázových jednotek, jako je tSQLt, spolu s nástroji pro testování jednotek třetích stran, včetně dbForge Unit Test.
Na jedné straně je výhodou použití testovacích nástrojů třetích stran to, že vývojový tým může okamžitě vytvářet a spouštět testy jednotek s přidanými funkcemi. Také přímé použití testovacího rámce vám dává větší kontrolu nad jednotkovými testy. Proto můžete přidat více funkcí do samotného rámce testování jednotek. V tomto případě však na to musí mít váš tým čas a určitou úroveň odborných znalostí.
Tento článek zkoumá některé standardní postupy, které nám mohou pomoci zlepšit způsob psaní testů databázových jednotek.
Nejprve si projdeme některé klíčové koncepty testování databázových jednotek.
Co je testování databázových jednotek
Podle Dave Greena testy databázových jednotek zajišťují, že malé jednotky databáze, jako jsou tabulky, pohledy, uložené procedury atd., fungují podle očekávání.
Testy databázových jednotek se zapisují za účelem ověření, zda kód splňuje obchodní požadavky.
Pokud například obdržíte požadavek jako „Knihovník (koncový uživatel) by měl být schopen přidávat nové knihy do knihovny (Management Information System)“, musíte myslet na použití jednotkových testů pro uloženou proceduru, abyste zkontrolovali, zda může přidat novou knihu do Knihy tabulka.
Někdy série testů jednotek zajistí, že kód splňuje požadavky. Proto většina rámců pro testování jednotek včetně tSQLt umožňuje seskupování souvisejících jednotkových testů do jedné testovací třídy namísto spouštění jednotlivých testů.
Princip AAA
Za zmínku stojí 3-krokový princip unit testování, který je standardní praxí při psaní unit testů. Princip AAA je základem pro testování jednotek a skládá se z následujících kroků:
- Uspořádat/sestavit
- Jednat
- Tvrdit
Uspořádat sekce je prvním krokem při psaní testů databázových jednotek. Provede vás konfigurací databázového objektu pro testování a nastavení očekávaných výsledků.
Zákon sekce je, když je volán databázový objekt (v testu), aby vytvořil skutečný výstup.
Prohlášení krok se zabývá porovnáním skutečného výstupu s očekávaným a ověřuje, zda test prošel nebo selhal.
Prozkoumejme tyto metody na konkrétních příkladech.
Pokud vytvoříme test jednotky, abychom ověřili, že AddProduct uložená procedura může přidat nový produkt, nastavíme Produkt a Očekávaný produkt tabulky po přidání produktu. V tomto případě metoda spadá do sekce Uspořádat/Sestavit.
Vyvolání procedury AddProduct a vložení výsledku do tabulky Product je pokryto sekcí Act.
Část Assert jednoduše porovnává tabulku Product s tabulkou ExpectedProduct, aby se zjistilo, zda byla uložená procedura úspěšně nebo neúspěšně provedena.
Porozumění závislostem při testování jednotek
Dosud jsme probrali základy testování databázových jednotek a význam principu AAA (Assemble, Act, and Assert) při vytváření standardního unit testu.
Nyní se zaměřme na další důležitý kousek skládačky – závislosti v testování jednotek.
Kromě dodržování principu AAA a zaměření se pouze na konkrétní databázový objekt (v testu), potřebujeme také znát závislosti, které mohou ovlivnit unit testy.
Nejlepší způsob, jak porozumět závislostem, je podívat se na příklad unit testu.
Nastavení vzorové databáze zaměstnanců
Chcete-li pokračovat, vytvořte vzorovou databázi a nazvěte ji EmployeesSample :
-- Create the Employees sample database to demonstrate unit testing CREATE DATABASE EmployeesSample; GO
Nyní vytvořte Zaměstnance tabulka ve vzorové databázi:
-- 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
Vyplnění ukázkových dat
Naplňte tabulku přidáním několika záznamů:
-- 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
Tabulka vypadá takto:
-- View the Employee table SELECT e.EmployeeId ,e.NAME ,e.StartDate ,e.Title FROM Employee e; GO
Vezměte prosím na vědomí, že v tomto článku používám dbForge Studio pro SQL Server. Vzhled výstupu se tedy může lišit, pokud stejný kód spustíte v SSMS (SQL Server Management Studio). Pokud jde o skripty a jejich výsledky, není žádný rozdíl.
Požadavek na přidání nového zaměstnance
Nyní, pokud byl přijat požadavek na přidání nového zaměstnance, nejlepším způsobem, jak tento požadavek splnit, je vytvořit uloženou proceduru, která může úspěšně přidat nového zaměstnance do tabulky.
Chcete-li to provést, vytvořte takto uloženou proceduru AddEmployee:
-- 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
Test jednotky pro ověření, zda je požadavek splněn
Napíšeme test databázové jednotky, abychom ověřili, zda uložená procedura AddEmployee splňuje požadavek na přidání nového záznamu do tabulky Employee.
Zaměřme se na pochopení filozofie testů jednotek tím, že nasimulujeme kód testování jednotek namísto psaní testu jednotek pomocí testovacího rámce nebo nástroje pro testování jednotek třetí strany.
Simulace testu jednotek a aplikace principu AAA v SQL
První věc, kterou musíme udělat, je napodobit princip AAA v SQL, protože nebudeme používat žádný rámec pro testování jednotek.
Sekce Sestavit se použije, když se skutečné a očekávané tabulky normálně nastavují spolu s očekávanou tabulkou, která se zaplňuje. V tomto kroku můžeme k inicializaci očekávané tabulky použít proměnné SQL.
Sekce Act se používá, když je volána skutečná uložená procedura k vložení dat do skutečné tabulky.
Sekce Assert je, když očekávaná tabulka odpovídá skutečné tabulce. Simulace části Assert je trochu složitější a lze ji dosáhnout pomocí následujících kroků:
- Počítání běžných (odpovídajících) řádků mezi dvěma tabulkami, které by měly být 1 (protože očekávaná tabulka má pouze jeden záznam, který by měl odpovídat skutečné tabulce)
- Vyloučení skutečných záznamů tabulky z očekávaných záznamů tabulky by se mělo rovnat 0 (pokud záznam v očekávané tabulce existuje také ve skutečné tabulce, pak vyloučení všech skutečných záznamů tabulky z očekávané tabulky by mělo vrátit 0)
SQL skript je následující:
[expand title=”Kód”]
-- 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]
Spuštění testu simulované jednotky
Po vytvoření uložené procedury ji spusťte pomocí testu simulované jednotky:
-- Running simulated unit test to check the AddEmployee stored procedure EXEC TestAddEmployee
Výstup je následující:
Gratulujeme! Test databázové jednotky prošel.
Identifikace problémů ve formě závislostí v testu jednotek
Můžeme v testu jednotky, který jsme vytvořili, zjistit něco špatného, přestože byl napsán a spuštěn úspěšně?
Pokud se podrobně podíváme na nastavení testu jednotek (část Assemble), očekávaná tabulka má zbytečnou vazbu se sloupcem identity:
Před napsáním unit testu jsme již přidali 5 záznamů do aktuální tabulky (Zaměstnanec). Při nastavení testu tedy sloupec identity pro očekávanou tabulku začíná číslem 6. To však znamená, že vždy očekáváme, že ve skutečné tabulce (Zaměstnanec) bude 5 záznamů, které budou odpovídat očekávané tabulce (#EmployeeExpected).
Abychom pochopili, jak to může ovlivnit test jednotky, podívejme se nyní na skutečnou tabulku (Zaměstnanci):
Přidejte další záznam do tabulky Zaměstnanec:
-- Adding a new record to the Employee table INSERT INTO Employee (NAME, StartDate, Title) VALUES ('Mark', '2018-02-01', 'Developer');
Podívejte se nyní na tabulku Zaměstnanci:
Odstraňte EmpoyeeId 6 (Adil), aby se test jednotky mohl spustit s vlastní verzí EmployeeId 6 (Adil), nikoli s dříve uloženým záznamem.
-- Deleting the previously created EmployeeId: 6 (Adil) record from the Employee table DELETE FROM Employee WHERE EmployeeId=6
Spusťte test simulované jednotky a podívejte se na výsledky:
-- Running the simulated unit test to check the AddEmployee stored procedure EXEC TestAddEmployee
Test tentokrát selhal. Odpověď se nachází v sadě výsledků tabulky Zaměstnanci, jak je uvedeno níže:
Vazba Id zaměstnance v testu jednotky, jak je uvedeno výše, nefunguje, když znovu spustíme test jednotky po přidání nového záznamu a smazání dříve přidaného záznamu zaměstnance.
V testu existují tři typy závislostí:
- Závislost na datech
- Závislost na klíčových omezeních
- Závislost sloupce identity
Závislost na datech
Za prvé, tento test jednotky závisí na datech v databázi. Podle Dave Greena, pokud jde o databázi testování jednotek, samotná data jsou závislost.
To znamená, že váš test databázové jednotky by se neměl spoléhat na data v databázi. Například váš unit test by měl obsahovat skutečná data, která mají být vložena do databázového objektu (tabulky), spíše než spoléhat na data již existující v databázi, která lze smazat nebo upravit.
V našem případě je skutečnost, že do aktuální tabulky Zaměstnanec již bylo vloženo pět záznamů, datovou závislostí, které je třeba zabránit, protože bychom neměli porušovat filozofii testu jednotek, která říká, že je testována pouze jednotka kódu.
Jinými slovy, testovací data by se neměla spoléhat na skutečná data v databázi.
Závislost na klíčovém omezení
Další závislostí je závislost na omezení klíče, což znamená, že sloupec primárního klíče EmployeeId je také závislost. Aby bylo možné napsat dobrý jednotkový test, je třeba tomu zabránit. K testování omezení primárního klíče je však vyžadován samostatný test jednotky.
Chcete-li například otestovat uloženou proceduru AddEmployee, měl by být primární klíč tabulky Employee odstraněn, aby bylo možné objekt testovat bez obav z porušení primárního klíče.
Závislost sloupce identity
Stejně jako omezení primárního klíče je i sloupec identity závislost. Není tedy potřeba testovat logiku automatického přírůstku sloupce identity pro proceduru AddEmployee; je třeba se tomu za každou cenu vyhnout.
Izolování závislostí při testování jednotek
Všem třem závislostem můžeme předejít tak, že omezení dočasně odstraníme z tabulky a pak nezávisíme na datech v databázi pro unit test. Takto jsou zapsány standardní testy databázových jednotek.
V tomto případě se lze zeptat, odkud se vzala data pro tabulku Zaměstnanci. Odpověď je, že tabulka se zaplní testovacími daty definovanými v testu jednotek.
Změna uložené procedury testu jednotky
Pojďme nyní odstranit závislosti v našem testu jednotek:
[expand title=”Kód”]
-- 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]
Spuštění testu simulované jednotky bez závislostí
Spusťte test simulované jednotky, abyste viděli výsledky:
-- Running the dependency-free simulated unit test to check the AddEmployee stored procedure EXEC TestAddEmployee
Spusťte znovu test jednotky a zkontrolujte uloženou proceduru AddEmployee:
-- Running the dependency-free simulated unit test to check the AddEmployee stored procedure EXEC TestAddEmployee
Gratulujeme! Závislosti z testu jednotky byly úspěšně odstraněny.
Nyní, i když přidáme nový záznam nebo sadu nových záznamů do tabulky Zaměstnanec, neovlivní to náš test jednotek, protože jsme z testu úspěšně odstranili závislosti na datech a omezeních.
Vytvoření testu databázových jednotek pomocí tSQLt
Dalším krokem je vytvoření skutečného testu databázových jednotek na základě simulovaného testu jednotek.
Pokud používáte SSMS (SQL Server Management Studio), budete si muset před napsáním a spuštěním testu jednotky nainstalovat rámec tSQLt, vytvořit testovací třídu a povolit CLR.
Pokud používáte dbForge Studio pro SQL Server, můžete vytvořit test jednotky kliknutím pravým tlačítkem na uloženou proceduru AddEmployee a poté kliknutím na „Test jednotky“ => „Přidat nový test…“, jak je uvedeno níže:
Chcete-li přidat nový test, vyplňte požadované informace o jednotkovém testu:
Chcete-li napsat test jednotky, použijte následující 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
Poté spusťte test databázové jednotky:
Gratulujeme! Úspěšně jsme vytvořili a spustili test databázových jednotek, který je bez závislostí.
Co dělat
A je to. Po prostudování tohoto článku jste připraveni izolovat závislosti z testů databázových jednotek a vytvořit test databázových jednotek bez závislostí na datech a omezeních. V důsledku toho můžete zlepšit své dovednosti prováděním následujících věcí:
- Zkuste přidat uloženou proceduru Delete Employee a vytvořte simulovaný test databázové jednotky pro Delete Employee se závislostmi, abyste zjistili, zda za určitých podmínek selže.
- Zkuste přidat uloženou proceduru Odstranit zaměstnance a vytvořte test databázové jednotky bez závislostí, abyste zjistili, zda lze zaměstnance smazat.
- Zkuste přidat uloženou proceduru Hledat zaměstnance a vytvořte simulovaný test databázové jednotky se závislostmi, abyste zjistili, zda lze zaměstnance vyhledat
- Zkuste přidat uloženou proceduru Hledat zaměstnance a vytvořte test databázové jednotky bez závislostí, abyste zjistili, zda lze zaměstnance vyhledat
- Vyzkoušejte složitější požadavky vytvořením uložených procedur, které požadavky splňují, a poté napsáním testů databázových jednotek bez závislostí, abyste zjistili, zda testem projdou nebo selžou. Ujistěte se však, že test je opakovatelný a zaměřený na testování jednotky kódu
Užitečný nástroj:
dbForge Unit Test – intuitivní a pohodlné GUI pro implementaci automatizovaného testování jednotek v SQL Server Management Studio.