Database
 sql >> база данни >  >> RDS >> Database

Изкуство за изолиране на зависимости и данни при тестване на единици от база данни

Всички разработчици на бази данни повече или по-малко пишат модулни тестове на база данни, които не само помагат за ранно откриване на грешки, но и спестяват много време и усилия, когато неочакваното поведение на обектите на базата данни се превърне в производствен проблем.

В днешно време съществуват редица рамки за тестване на база данни, като tSQLt, заедно с инструменти за тестване на модули на трети страни, включително dbForge Unit Test.

От една страна, ползата от използването на инструменти за тестване на трети страни е, че екипът за разработка може незабавно да създава и изпълнява модулни тестове с добавени функции. Също така, използването на рамка за тестване директно ви дава повече контрол върху тестовете за единици. Следователно можете да добавите повече функционалност към самата рамка за тестване на модули. В този случай обаче вашият екип трябва да има време и определено ниво на опит, за да направи това.

Тази статия изследва някои стандартни практики, които могат да ни помогнат да подобрим начина, по който пишем единични тестове за база данни.

Първо, нека преминем през някои ключови концепции за тестване на база данни.

Какво е тестване на база данни

Според Дейв Грийн тестовете за единици на базата данни гарантират, че малките единици от базата данни, като таблици, изгледи, съхранени процедури и т.н., работят според очакванията.

Единичните тестове на базата данни са написани, за да се провери дали кодът отговаря на бизнес изискванията.

Например, ако получите изискване като „Библиотекар (крайният потребител) трябва да може да добавя нови книги към библиотеката (система за информация за управление)“, трябва да помислите за прилагане на тестове за единици за съхранената процедура, за да проверите дали може да добави нова книга към Книгата таблица.

Понякога серия от единични тестове гарантира, че кодът отговаря на изискванията. Следователно повечето рамки за тестване на модули, включително tSQLt, позволяват групиране на свързаните модулни тестове в един тестов клас, вместо да се изпълняват отделни тестове.

Принцип AAA

Струва си да се спомене за 3-степенния принцип на тестване на единици, който е стандартна практика за писане на тестове за единици. Принципът AAA е основата за тестване на единици и се състои от следните стъпки:

  1. Подредете/сглобете
  2. Действие
  3. Потвърждение

Подреждане Разделът е първата стъпка в писането на единични тестове за база данни. Той води през конфигурирането на обект на база данни за тестване и настройка на очакваните резултати.

Законът раздел е, когато обект на база данни (в тестване) се извиква, за да произведе действителния изход.

Подтверди стъпка се занимава със съпоставянето на действителния изход с очаквания и проверява дали тестът е минал или неуспешен.

Нека разгледаме тези методи на конкретни примери.

Ако създадем единичен тест, за да проверим, че AddProduct съхранената процедура може да добави нов продукт, ние настройваме Продукта и Очакван продукт таблици след добавяне на продукта. В този случай методът попада в секцията Подреждане/Сглобяване.

Извикването на процедурата AddProduct и поставянето на резултата в таблицата с продуктите се покрива от раздела Act.

Частта Assert просто съпоставя таблицата Product с таблицата ExpectedProduct, за да види дали съхранената процедура е била изпълнена успешно или неуспешна.

Разбиране на зависимостите в тестването на модули

Досега обсъдихме основите на тестването на база данни с единици и значението на принципа AAA (сглобяване, действие и потвърждаване) при създаването на стандартен тест за единица.

Сега, нека се съсредоточим върху друга важна част от пъзела – зависимостите в тестването на модули.

Освен че следваме принципа на AAA и се фокусираме само върху конкретен обект на база данни (в процес на тестване), ние също трябва да знаем зависимостите, които могат да повлияят на модулните тестове.

Най-добрият начин да разберете зависимостите е да разгледате пример за единичен тест.

Настройка на базата данни EmployeesSample

За да продължите, създайте примерна база данни и я наречете EmployeesSample :

-- Create the Employees sample database to demonstrate unit testing

CREATE DATABASE EmployeesSample;
GO

Сега създайте Служител таблица в примерната база данни:

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

Попълване на примерни данни

Попълнете таблицата, като добавите няколко записа:

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

Таблицата изглежда така:

-- View the Employee table

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

Моля, обърнете внимание, че използвам dbForge Studio за SQL Server в тази статия. По този начин изходният вид може да се различава, ако стартирате същия код в SSMS (SQL Server Management Studio). Няма разлика, когато става дума за скриптове и техните резултати.

Изискване за добавяне на нов служител

Сега, ако е получено изискване за добавяне на нов служител, най-добрият начин да изпълните изискването е да създадете съхранена процедура, която може успешно да добави нов служител към таблицата.

За да направите това, създайте съхранената процедура 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

Единичен тест за проверка дали изискването е изпълнено

Ще напишем единичен тест за база данни, за да проверим дали съхранената процедура AddEmployee отговаря на изискването за добавяне на нов запис към таблицата Employee.

Нека се съсредоточим върху разбирането на философията на модулния тест, като симулираме код за модулен тест, вместо да пишем единичен тест с рамка за тестване или инструмент за тестване на модули на трета страна.

Симулиране на единичен тест и прилагане на AAA принцип в SQL

Първото нещо, което трябва да направим, е да имитираме принципа на AAA в SQL, тъй като няма да използваме никаква рамка за тестване на модули.

Разделът Асемблиране се прилага, когато действителните и очакваните таблици обикновено се настройват заедно с попълването на очакваната таблица. Можем да използваме SQL променливи, за да инициализираме очакваната таблица в тази стъпка.

Секцията Act се използва, когато действителната съхранена процедура се извиква за вмъкване на данни в действителната таблица.

Разделът Assert е когато очакваната таблица съвпада с действителната таблица. Симулирането на частта Assert е малко сложно и може да бъде постигнато чрез следните стъпки:

  • Преброяване на общите (съвпадащи) редове между две таблици, които трябва да са 1 (тъй като очакваната таблица има само един запис, който трябва да съвпада с действителната таблица)
  • Изключването на действителните записи на таблицата от очакваните записи в таблицата трябва да е равно на 0 (ако записът в очакваната таблица съществува и в действителната таблица, тогава изключването на всички действителни записи на таблицата от очакваната таблица трябва да върне 0)

SQL скриптът е както следва:

[expand title=”Код”]

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

Извършване на симулиран единичен тест

След като съхранената процедура е създадена, изпълнете я със симулирания единичен тест:

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

Резултатът е както следва:

Честито! Тестът на базата данни е преминал.

Идентифициране на проблеми под формата на зависимости в юнит тест

Можем ли да открием нещо нередно в единичния тест, който създадохме, въпреки факта, че е написан и стартиран успешно?

Ако разгледаме отблизо настройката на единичния тест (частта Assemble), очакваната таблица има ненужно обвързване с колоната за идентичност:

Преди да напишем единичен тест, вече сме добавили 5 записа към действителната таблица (Служители). По този начин при тестовата настройка колоната за идентичност за очакваната таблица започва с 6. Това обаче означава, че винаги очакваме 5 записа да бъдат в действителната (Служител) таблица, за да я съпоставят с очакваната таблица (#EmployeeExpected).

За да разберем как това може да повлияе на единичния тест, нека разгледаме действителната таблица (Служители) сега:

Добавете друг запис към таблицата на служителите:

-- Adding a new record to the Employee table

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

Разгледайте таблицата на служителите сега:

Изтрийте EmpoyeeId 6 (Adil), за да може единичният тест да работи срещу собствената си версия на EmployeeId 6 (Adil), а не със съхранения по-рано запис.

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

DELETE FROM Employee
  WHERE EmployeeId=6

Изпълнете симулирания тест за единица и вижте резултатите:

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

Този път тестът се провали. Отговорът се крие в набора от резултати от таблицата на служителите, както е показано по-долу:

Обвързването на идентификационния номер на служител в теста за единица, както бе споменато по-горе, не работи, когато стартираме повторно теста за единица, след като добавим нов запис и изтрием по-рано добавения запис на служител.

Има три типа зависимости в теста:

  1. Зависимост от данни
  2. Зависимост от ключово ограничение
  3. Зависимост на колоната за идентичност

Зависимост от данни

На първо място, този единичен тест зависи от данните в базата данни. Според Дейв Грийн, когато става въпрос за база данни за тестване на единици, самите данни са зависимост.

Това означава, че вашият единичен тест за база данни не трябва да разчита на данните в базата данни. Например, вашият единичен тест трябва да съдържа действителните данни, които трябва да бъдат вмъкнати в обекта на базата данни (таблица), а не да разчита на данните, които вече съществуват в базата данни, които могат да бъдат изтрити или променени.

В нашия случай фактът, че пет записа вече са били вмъкнати в действителната таблица Employee, е зависимост от данни, която трябва да бъде предотвратена, тъй като не трябва да нарушаваме философията на единичния тест, която казва, че се тества само единицата от кода.

С други думи, тестовите данни не трябва да разчитат на действителните данни в базата данни.

Зависимост от ключово ограничение

Друга зависимост е зависимост от ключово ограничение, което означава, че колоната EmployeeId на първичния ключ също е зависимост. Трябва да се предотврати, за да се напише добър единичен тест. Въпреки това, за тестване на ограничение за първичен ключ е необходим отделен тест за единица.

Например, за да се тества съхранената процедура AddEmployee, основният ключ на таблицата Employee трябва да бъде премахнат, за да може даден обект да бъде тестван, без да се притеснявате от нарушаване на първичен ключ.

Зависимост на колоната за идентичност

Точно като ограничение за първичен ключ, колоната за идентичност също е зависимост. По този начин няма нужда от тестване на логиката за автоматично увеличаване на колоната за идентичност за процедурата AddEmployee; трябва да се избягва на всяка цена.

Изолиране на зависимости в тестване на модули

Можем да предотвратим и трите зависимости, като премахнем временно ограниченията от таблицата и след това не зависим от данните в базата данни за единичния тест. Ето как са написани стандартните тестове на база данни.

В този случай може да се попита откъде идват данните за таблицата на служителите. Отговорът е, че таблицата се попълва с тестови данни, дефинирани в единичния тест.

Промяна на запаметената процедура за тест на единица

Нека сега премахнем зависимостите в нашия единичен тест:

[expand title=”Код”]

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

Извършване на симулиран единичен тест без зависимости

Изпълнете симулирания единичен тест, за да видите резултатите:

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

EXEC TestAddEmployee

Изпълнете отново единичния тест, за да проверите съхранената процедура AddEmployee:

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

EXEC TestAddEmployee

Честито! Зависимостите от единичния тест бяха премахнати успешно.

Сега, дори ако добавим нов запис или набор от нови записи към таблицата Employee, това няма да повлияе на нашия единичен тест, тъй като успешно премахнахме зависимостите от данни и ограничения от теста.

Създаване на единичен тест за база данни с помощта на tSQLt

Следващата стъпка е да създадете реален тест за единица база данни, базиран на симулирания единичен тест.

Ако използвате SSMS (SQL Server Management Studio), ще трябва да инсталирате рамката tSQLt, да създадете тестов клас и да активирате CLR, преди да напишете и стартирате единичния тест.

Ако използвате dbForge Studio за SQL Server, можете да създадете единичния тест, като щракнете с десния бутон върху съхранената процедура AddEmployee и след това щракнете върху „Unit Test“ => „Добавяне на нов тест…“, както е показано по-долу:

За да добавите нов тест, попълнете необходимата информация за единичния тест:

За да напишете единичния тест, използвайте следния скрипт:

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

След това стартирайте теста на базата данни:

Честито! Успешно създадохме и стартирахме единичен тест на базата данни, който не съдържа зависимости.

Неща за правене

Това е. Готови сте да изолирате зависимости от единични тестове на база данни и да създадете единичен тест на база данни без зависимости от данни и ограничения, след като преминете през тази статия. В резултат на това можете да подобрите уменията си, изпълнявайки следните неща:

  1. Моля, опитайте да добавите съхранената процедура Delete Employee и създайте симулиран тест за единица база данни за Delete Employee със зависимости, за да видите дали не успее при определени условия
  2. Моля, опитайте да добавите съхранената процедура Delete Employee и създайте тест за единица база данни без зависимости, за да видите дали даден служител може да бъде изтрит
  3. Моля, опитайте да добавите съхранената процедура Search Employee и създайте симулиран тест за единица база данни със зависимости, за да видите дали служител може да бъде търсен
  4. Моля, опитайте да добавите съхранената процедура Search Employee и създайте тест за единица база данни без зависимости, за да видите дали служител може да бъде търсен
  5. Моля, опитайте по-сложни изисквания, като създадете съхранени процедури, за да отговарят на изискванията, и след това напишете тестове на база данни без зависимости, за да видите дали издържат теста или се провалят. Въпреки това, моля, уверете се, че тестът е повторяем и фокусиран върху тестване на единицата на кода

Полезен инструмент:

dbForge Unit Test – интуитивен и удобен GUI за внедряване на автоматизирано тестване на модули в SQL Server Management Studio.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Мониторинг на архивиране в различни инстанции

  2. Миграция на SQL бази данни с команден ред

  3. Как да създадете документ на Excel от програма на Java с помощта на Apache POI

  4. Съпоставяне на предлагането с търсенето — Решения, част 3

  5. Задаване на физическа готовност за активна защита на данните в архитектурата с един възел на RAC – част 2