Данная библиотека предназначена для модульного и интеграционного тестирование приложений в основе, которых лежит бизнес-логика, зашитая в PL/PgSQL код.
Библиотека состоит из десятка хранимых процедуры, необходимых для минимального функционирования тестовой инфраструктуры.
Надо протестировать работу хранимой процедуры get_cart_item_count(), которая возвращает количество товаров в корзине покупателя.
CREATE OR REPLACE FUNCTION __test_get_cart_item_count() RETURNS void AS
$BODY$
DECLARE
_count int; -- Кол-во товаров в корзине, возращенное процедурой get_cart_item_count()
_i int; -- Счетчик в цикле
_ITEM_COUNT CONSTANT int := 7; -- Ожидаемое кол-во товаров в корзине
BEGIN
-- Тут может быть код, создающий тестового покупателя
...
PERFORM clear_cart(...); -- Очистка корзины
FOR _i IN 1.._ITEM_COUNT -- Добавление в корзину 7 товаров
LOOP
PERFORM add_item_to_cart(...);
END LOOP;
_count := get_cart_item_count(...); -- Получение числа товаров в корзине
PERFORM pgunit.assert_equals(_ITEM_COUNT, _count, 'Incorrect value'); -- Проверка значения
END
$BODY$ LANGUAGE plpgsql;
Теперь надо запустить тестовую процедуру внутри специальной процедуры, которая может выполнять дополнительные операции.
SELECT pgunit.run_test('__test_get_cart_item_count()');
---
#OK
Тест прошел успешно.
Благодаря тому, что процедура-обертка run_test откатывает данные, которые были созданы, изменены, удалены в процедуре тестирования, то после выполнения теста база остаётся в том же состоянии, в котором она была до начала тестирования.
Ниже располагается документация по установке и использованию библиотеки.
Скачать библиотеку можно с GitHub'а - https://github.com/danblack/pgunit-sql/archive/master.zip
Установка производится с помощью утилиты psql, которую надо запустить из папки библиотеки
$ psql -U sysdba database_to_test < reinstall.sql
Данный способ установки гарантирует, что все процедуры установились без ошибок, так как в процессе установки происходит самотестирование библиотеки.
В основе библиотеки лежат хранимые процедуры, выполняющие сравнения переменных между собой или сравнение переменных с различными константами.
Сравнивает два элемента. Если они не равны, то формируется исключение #assert_equalsn{custom_message}
Сравнивает два элемента. Если они равны, то формируется исключение #assert_not_equalsn{custom_message}
Сравнивает два массива. Массивы считаются равными, если эти массивы имеют одинаковые элементы и размеры массивов равны. Если массивы не равны, то генерируется исключение с текстом #assert_array_equalsn{custom_message}
Сравнивает _value c True. Если они не равны, то формируется исключение #assert_truen{custom_message}
Сравнивает _value c False. Если они не равны, то формируется исключение #assert_falsen{custom_message}
Сравнивает _value c NULL. Если они не равны, то формируется исключение #assert_nulln{custom_message}
Сравнивает _value c NULL. Если они равны, то формируется исключение #assert_not_nulln{custom_message}
Генерирует исключение с текстом #assert_failn{custom_message}
Запускает указанную хранимую процедуру внутри тестовой инфраструктуры. После запуска тестовой процедуры происходит откат данных.
Этот тип теста можно отнести к модульному, так как тестируется только математика.
Процедура, которую надо протестировать.
CREATE OR REPLACE FUNCTION get_vat_amount(_cost numeric, _pct numeric) RETURNS numeric AS
$BODY$
DECLARE
BEGIN
IF _cost IS NULL OR _pct IS NULL THEN
RAISE EXCEPTION '#error:param_is_null';
END IF;
IF _pct = 0 THEN
RAISE EXCEPTION '#error:vat_is_zero';
END IF;
RETURN _cost * _pct / 100;
END
$BODY$ LANGUAGE plpgsql;
Процедура тестирования.
CREATE OR REPLACE FUNCTION __test_vat() RETURNS void AS
$BODY$
DECLARE
_vat numeric;
BEGIN
-----------------------------------------
RAISE DEBUG '#trace Check a simple case';
-----------------------------------------
_vat := get_vat_amount(200, 18);
PERFORM pgunit.assert_equals(36::numeric, _vat, 'VAT is not 36'); -- Compare a returned vat and an expected value
-------------------------------------------
RAISE DEBUG '#trace Check null parameters';
-------------------------------------------
BEGIN
PERFORM get_vat_amount(NULL, NULL);
PERFORM pgunit.fail('Parameters are null'); -- Raise an error if get_vat_amount runs without an exception
EXCEPTION
WHEN others THEN
IF SQLERRM <> '#error:param_is_null' THEN -- Check an error text
RAISE; -- It's not an expected error, throw an exception forward
END IF;
END;
BEGIN
PERFORM get_vat_amount(100, NULL);
PERFORM pgunit.fail('Parameters are null');
EXCEPTION
WHEN others THEN
IF SQLERRM <> '#error:param_is_null' THEN
RAISE;
END IF;
END;
BEGIN
PERFORM get_vat_amount(NULL, 18);
PERFORM pgunit.fail('Parameters are null');
EXCEPTION
WHEN others THEN
IF SQLERRM <> '#error:param_is_null' THEN
RAISE;
END IF;
END;
_vat := get_vat_amount(0, 18);
PERFORM pgunit.assert_equals(0::numeric, _vat, 'VAT is not zero');
BEGIN
PERFORM get_vat_amount(100, 0);
PERFORM pgunit.fail('Zero VAT');
EXCEPTION
WHEN others THEN
IF SQLERRM <> '#error:vat_is_zero' THEN
RAISE;
END IF;
END;
---------------------------------------
RAISE DEBUG '#trace Check other cases';
---------------------------------------
_vat := get_vat_amount(300, 18);
PERFORM pgunit.assert_equals(54::numeric, _vat, 'VAT is not 54');
END
$BODY$ LANGUAGE plpgsql;
Запуск теста.
SELECT pgunit.run_test('__test_vat()');
---
#OK
Этот тест можно отнести к интеграционным, так как тестирование проводится в окружении, приближенном к реальному.
Схема базы данных
CREATE TABLE customers(
id serial PRIMARY KEY,
name varchar NOT NULL UNIQUE
);
CREATE TABLE payments(
id serial PRIMARY KEY,
date date NOT NULL,
customer_id int NOT NULL REFERENCES customers(id),
amount numeric(19, 2) NOT NULL
);
CREATE TABLE orders(
id serial PRIMARY KEY,
date date NOT NULL,
customer_id int NOT NULL REFERENCES customers(id),
amount numeric(19, 2) NOT NULL
);
Процедура расчета баланса
CREATE OR REPLACE FUNCTION get_balance(_customer_id int, _date date) RETURNS numeric AS
$BODY$
DECLARE
_payment_amount numeric;
_order_amount numeric;
BEGIN
IF NOT EXISTS(SELECT 1 FROM customers WHERE id = _customer_id) THEN
RAISE EXCEPTION '#error:customer_not_found';
END IF;
SELECT COALESCE(sum(amount), 0) INTO _payment_amount FROM payments WHERE customer_id = _customer_id AND date < _date;
SELECT COALESCE(sum(amount), 0) INTO _order_amount FROM orders WHERE customer_id = _customer_id AND date < _date;
RETURN _payment_amount - _order_amount;
END
$BODY$ LANGUAGE plpgsql;
Процедура тестирования.
CREATE OR REPLACE FUNCTION __test_get_balance() RETURNS void AS
$BODY$
DECLARE
_customer1_id int;
_customer2_id int;
_customer3_id int;
_balance numeric;
BEGIN
---------------------------------------
RAISE DEBUG '#trace Prepare customers';
---------------------------------------
INSERT INTO customers(name) VALUES('customer1')
RETURNING id INTO _customer1_id;
INSERT INTO customers(name) VALUES('customer2')
RETURNING id INTO _customer2_id;
_customer3_id = -1;
----------------------------------------
RAISE DEBUG '#trace Check zero balance';
----------------------------------------
_balance := get_balance(_customer1_id, 'infinity');
PERFORM pgunit.assert_equals(0::numeric, _balance, 'Balance is not zero');
-------------------------------------------------
RAISE DEBUG '#trace Check not existing customer';
-------------------------------------------------
BEGIN
_balance := get_balance(_customer3_id, 'infinity');
PERFORM pgunit.fail('Found not existing customer');
EXCEPTION
WHEN others THEN
IF SQLERRM <> '#error:customer_not_found1' THEN
RAISE;
END IF;
END;
---------------------------------------------------------
RAISE DEBUG '#trace Check a customer with payments only';
---------------------------------------------------------
INSERT INTO payments(customer_id, date, amount) VALUES(_customer1_id, '2020-01-02', 312);
_balance := get_balance(_customer1_id, '-infinity');
PERFORM pgunit.assert_equals(0::numeric, _balance, '');
_balance := get_balance(_customer1_id, 'infinity');
PERFORM pgunit.assert_equals(312::numeric, _balance, '');
_balance := get_balance(_customer1_id, '2020-01-02');
PERFORM pgunit.assert_equals(0::numeric, _balance, '');
_balance := get_balance(_customer1_id, '2020-01-03');
PERFORM pgunit.assert_equals(312::numeric, _balance, '');
-- ... continue
-------------------------------------------------------
RAISE DEBUG '#trace Check a customer with orders only';
-------------------------------------------------------
INSERT INTO orders(customer_id, date, amount) VALUES(_customer2_id, '2020-01-02', 231);
_balance := get_balance(_customer2_id, '-infinity');
PERFORM pgunit.assert_equals(0::numeric, _balance, '');
_balance := get_balance(_customer2_id, 'infinity');
PERFORM pgunit.assert_equals(-231::numeric, _balance, '');
_balance := get_balance(_customer2_id, '2020-01-02');
PERFORM pgunit.assert_equals(0::numeric, _balance, '');
_balance := get_balance(_customer2_id, '2020-01-03');
PERFORM pgunit.assert_equals(-231::numeric, _balance, '');
-- ... so on
-------------------------------------------------------------
RAISE DEBUG '#trace Check customer with payments and orders';
-------------------------------------------------------------
INSERT INTO orders(customer_id, date, amount) VALUES(_customer1_id, '2020-01-01', 231);
_balance := get_balance(_customer1_id, '-infinity');
PERFORM pgunit.assert_equals(0::numeric, _balance, '');
_balance := get_balance(_customer1_id, '2020-01-01');
PERFORM pgunit.assert_equals(0::numeric, _balance, '');
_balance := get_balance(_customer1_id, '2020-01-02');
PERFORM pgunit.assert_equals(-231::numeric, _balance, '');
_balance := get_balance(_customer1_id, '2020-01-03');
PERFORM pgunit.assert_equals(81::numeric, _balance, '');
-- ... continue yourself
END
$BODY$ LANGUAGE plpgsql;
Запуск теста.
SELECT pgunit.run_test('__test_get_balance()');
---
#OK