Джино: хостинг и веб-сервисы

Система Orphus
Russian version
Добавить на Del.icio.us
English version
Добавить на Digg.com

 dkLab | Конструктор | PGUnit: unit-test фреймворк для хранимых процедур PostgreSQL 8.3 

Карта сайта :: Форум «Лаборатории» :: Проект «Денвер»
Проект «Orphus» :: Куроводство: наблы :: Конструктор


2008-11-03

Скачать дистрибутив PGUnit для PostgreSQL 8.3:
dklab_pgunit_2008-11-09.sql

Что такое PGUnit?

PGUnit — это фреймворк для написания unit-тестов в стиле xUnit для PostgreSQL 8.3+. Он позволяет разработчикам хранимых процедур писать автоматические unit-тесты для существующих процедур, а также создавать новые процедуры согласно концепции Test Driven Development (TDD). Код тестов записывается в саму базу данных, поэтому для хранения и запуска тестов нет необходимости использовать внешние ресурсы (такие как файлы, системы контроля версий, утилиты командной строки и т. д.).

Как и в традиционном xUnit, набор тестов группируется в "тест-кейсы"; каждый кейс может иметь свой собственный код инициализации окружения, общего для всех тестов группы ("код подготовки фикстуры" или "блок setUp"). Главное преимущество PGUnit в том, что блок setUp (часто довольно тяжеловесный) выполняется только один раз в рамках тест-кейса, а эффект от его выполнения сохраняется в savepoint базы данных. В дальнейшем все тесты исполняются начиная с этой savepoint, так что накладные расходы на инициализацию фикстуры минимальны. (Иными словами, фикстура инициализируется только однажды, но результат этой инициализации сохраняется и применяется многократно.) При этом все тесты по прежнему запускаются независимо друг от друга, потому что эффект от запуска теста автоматически "откатывается" после его исполнения.

Как установить

Это просто: скачайте и запустите как обычный SQL-скрипт. В вашей базе данных появится новая схема с именем "pgunit"; она содержит все необходимые функции.

Лирическое отступление 
А как же pgTAP? Зачем еще один тестовый фреймворк?
Дело в том, что pgTAP для нас оказался недостаточно прозрачным. Но, конечно, возможно, что вам следует использовать pgTAP, а не PGUnit - изучите его тоже.

Как пользоваться

Собственно, PGUnit работает точно так же, как и любые xUnit-фреймворки (например, PHPUnit). Так что, научившись работать с тем же PHPUnit, вы легко поймете и принципы PGUnit.

Чайник 

Если вы ни разу не работали с unit-тестами, рекомендую внимательно изучить
документацию PHPUnit.

Типичный процесс использования PGUnit заключается в следующем.

  1. Пишется процедура с тестовым кодом и именем test_* для вашей базы данных (см. примеры ниже).
  2. Запускается команда:
    SELECT pgunit.testrunner(NULL);
    Эта команда сканирует БД на наличие в ней процедур с именем test_* (и соответствующим типом возвращаемого значения), а затем - последовательно запускает их на выполнения, каждую - в изолированной транзакции.
  3. Можно также выполнить не все, а только некоторые тесты:
    SELECT pgunit.testrunner('first%testname second%testname ...')
    Разрешено использовать подстановочные знаки оператора LIKE, а также перечислять несколько имен тестов через пробел.
  4. Результаты выполнения тестового кода "откатываются" и не оставляют в БД следа.

Пример тестового кода

Все достаточно просто. Вам нужно создать специальную функцию на языке SQL и определить в ней тестовые кусочки кода. Как и в традиционных unit-тестах, имеется секция setUp, выполняющаяся перед каждым тестом и запускающая общую для всех тестов инициализацию, и набор именованных кусков тестового кода, которые работают изолированно друг от друга.

Чайник 

Важно понимать, что на логическом уровне код в секции setUp выполняется столько раз, сколько существует кусков тестового кода. Т.е. эффект от запуска setUp как бы "приклеивается" к коду теста и выполняется в единой с ним транзакции.

CREATE FUNCTION pgunit.test_sample () RETURNS testfunc[]
AS
$body$
SELECT pgunit.testcase(
    $setUp$
        -- setUp code is executed before ANY test function code (see below).
        -- Effect of this execution is persistent only during the code
        -- block execution and rolled back after the test is finished.
        CREATE TABLE tst(id INTEGER);
    $setUp$,
    ARRAY[
        -- This is a first test function code. Just check if we can insert
        -- into the table created in setUp.
        'first test: insert is okay', $sql$
            INSERT INTO tst VALUES(1);
            PERFORM pgunit.assert_same(1, (SELECT * FROM tst));
        $sql$,

        -- This is a second test function code.
        -- Illustrates that the effect of the first function is not visible.
        'second test: effect of previous function is not visible here', $sql$
            PERFORM pgunit.assert_same(NULL, (SELECT * FROM tst));
        $sql$,

        -- This is a third test function code. Illustrate that we may use DECLARE.
        'first test: you may use DECLARE in tests', $sql$
            DECLARE
                i INTEGER;
            BEGIN
                FOR i IN 1 .. 10 LOOP
                    INSERT INTO tst VALUES(i);
                    PERFORM pgunit.assert_same(i, (SELECT * FROM tst WHERE id = i));
                END LOOP;
            END;
        $sql$
    ]
);
$body$
    LANGUAGE sql;

Пример результата работы

А вот пример результата работы тестов, запущенных командой SELECT pgunit.testrunner(NULL). Я специально "поломал" один тест, чтобы показать, как это выглядит. Формат вывода совместим с PHPUnit.

NOTICE:  PGUnit 2008-11-03 by Dmitry Koterov.
NOTICE:  
NOTICE:  pgunit.test_assert_same
NOTICE:    - OK matchedValues
NOTICE:    - OK mismatchedValues
NOTICE:  pgunit.test_exec
NOTICE:    ! FAIL executed
NOTICE:  pgunit.test_runtest
NOTICE:    - OK executed
NOTICE:    - OK exception hook
NOTICE:  pgunit.test_sample
NOTICE:    - OK first test: insert is okay
NOTICE:    - OK second test: effect of previous function is not visible here
NOTICE:  pgunit.test_wildcard_match
NOTICE:    - OK one value
NOTICE:    - OK two values
NOTICE:    - OK no matches
NOTICE:  
NOTICE:  Time: 00:00:00
NOTICE:  
NOTICE:  There was 1 failure
NOTICE:  
NOTICE:  1) executed(pgunit.test_exec)
NOTICE:  Failed asserting that two things are same.
NOTICE:  expected string <2>
NOTICE:  got string      <1>
NOTICE:  
NOTICE:  FAILURES!
NOTICE:  Tests: 10, Failures: 1.

Changelog

2008-11-09
  - A little code simplification (undocumented "WHEN others THEN ..." 
    instead of exception class enumeration).

2008-11-07
  - Great speed-up while running a bunch of test with a single setUp block.
    In this case setUp is executed ONLY ONCE, and the effect of its 
    execution is stored in a savepoint which is used for all 
    individually-rollbacked tests. (All tests are still running 
    independently and are not intersected with each other.)
  - Time measurnment of each test execution (thanks to Garrynja).
  - Supress NOTICEs generated by a setUp or test code to keep the 
    execution log clean.

2008-11-03
  - First release.






Дмитрий Котеров, Лаборатория dk. ©1999-2016
GZip
Добавить на Del.icio.us   Добавить на Digg.com   Добавить на reddit.com
1) Warning: Subroutine Pager::basename redefined at /usr/share/perl/5.18/Exporter.pm line 66, <F> line 1.
2) Warning: Subroutine Pager::dirname redefined at /usr/share/perl/5.18/Exporter.pm line 66, <F> line 1.