pgtap是一個使用PL/pgSQL和PL/SQL編寫的單元測試架構,是PolarDB PostgreSQL版的一個TAP測試架構,包括一個全面的TAP斷言功能集合,並具備整合其他TAP測試架構的能力。
前提條件
支援的PolarDB PostgreSQL版的版本如下:
PostgreSQL 14(核心小版本14.5.3.0及以上)
PostgreSQL 11(核心小版本1.1.30及以上)
您可通過如下語句查看PolarDB PostgreSQL版的核心小版本號碼:
PostgreSQL 14
SELECT version();PostgreSQL 11
SHOW polar_version;
術語
單元測試(Unit Testing): 又稱模組測試,允許針對系統的不同模組進行正確性檢驗的測試工作。
TAP(Test Anything Protocol): 最初為Perl語言開發,提供了一種將測試結果傳達給測試載入器的機制,同時保持語言無關性並簡化了測試過程中的錯誤報表形式。
使用方法
需要使用超級使用者安裝pgtap外掛程式,如您有相應需求,請聯絡我們處理。
安裝外掛程式
CREATE EXTENSION pgtap;卸載外掛程式
DROP EXTENSION pgtap;
測試樣本
pgtap外掛程式提供了關於資料表空間、模式、表、列、視圖、序列、索引、觸發器、函數、策略、使用者、語言、規則、類型、操作符、擴充等豐富的測試方法。
以表、策略、列、函數為例,對pgtap外掛程式的使用方法進行具體的介紹。
表/索引/視圖測試
如果需要檢測某個表/索引/視圖是否存在,可以參考以下測試樣本:
BEGIN; SELECT plan(6); -- 檢測表 "tap_table" 是否存在 SELECT has_table('tap_table'); -- 檢測表 "tap_table_non_exist" 是否不存在 SELECT hasnt_table('tap_table_non_exist'); -- 檢測視圖 "tap_view" 是否存在 SELECT has_view('tap_view'); -- 檢測物化視圖 "materialized_tap_view" 是否存在 SELECT has_materialized_view('materialized_tap_view'); -- 檢測表 "tap_table" 是否存在 "tap_table_index" 索引 SELECT has_index('tap_table', 'tap_table_index'); -- 檢測 "tap_table" 是否為relation SELECT has_relation('tap_table'); SELECT * FROM finish(); ROLLBACK;說明其中:
has_table和hasnt_table用於檢測指定的表是否存在。has_view用於檢測指定的視圖是否存在。has_materialized_view用於檢測指定的物化視圖是否存在。has_index用於檢測表是否包含對應的索引。has_relation用於檢測是否存在指定的relation,包括表、索引、序列等。
通過執行上述命令可以獲得以下返回結果。發現相應的測試均失敗了,這是由於指定的表、索引等對象均不存在。
has_table ------------------------------------------------- not ok 1 - Table tap_table should exist + # Failed test 1: "Table tap_table should exist" (1 row) hasnt_table --------------------------------------------------- ok 2 - Table tap_table_non_exist should not exist (1 row) has_view ----------------------------------------------- not ok 3 - View tap_view should exist + # Failed test 3: "View tap_view should exist" (1 row) has_materialized_view ------------------------------------------------------------------------- not ok 4 - Materialized view materialized_tap_view should exist + # Failed test 4: "Materialized view materialized_tap_view should exist" (1 row) has_index ------------------------------------------------------- not ok 5 - Index tap_table_index should exist + # Failed test 5: "Index tap_table_index should exist" (1 row) has_relation ---------------------------------------------------- not ok 6 - Relation tap_table should exist + # Failed test 6: "Relation tap_table should exist" (1 row) finish -------------------------------------- # Looks like you failed 5 tests of 6 (1 row)執行以下命令,建立相應的表/索引/視圖:
CREATE TABLE tap_table(col INT PRIMARY KEY, tap_desc TEXT); CREATE INDEX tap_table_index on tap_table(col); CREATE VIEW tap_view AS SELECT * FROM tap_table; CREATE MATERIALIZED VIEW materialized_tap_view AS SELECT * FROM tap_table;當表/索引/視圖建立成功後,再次執行上述TAP測試,結果如下所示:
has_table ------------------------------------- ok 1 - Table tap_table should exist (1 row) hasnt_table --------------------------------------------------- ok 2 - Table tap_table_non_exist should not exist (1 row) has_view ----------------------------------- ok 3 - View tap_view should exist (1 row) has_materialized_view ------------------------------------------------------------- ok 4 - Materialized view materialized_tap_view should exist (1 row) has_index ------------------------------------------- ok 5 - Index tap_table_index should exist (1 row) has_relation ---------------------------------------- ok 6 - Relation tap_table should exist (1 row) finish -------- (0 rows)RLS策略測試
如果需要檢測表是否存在某個行層級安全(Row-Level Security)策略,可以參考以下測試樣本:
CREATE USER tap_user_1; CREATE USER tap_user_2; CREATE TABLE tap_table(col INT PRIMARY KEY, tap_desc TEXT); CREATE POLICY tap_policy ON tap_table FOR select TO tap_user_1, tap_user_2; BEGIN; SELECT plan(5); SELECT policy_cmd_is( 'public', 'tap_table', 'tap_policy'::NAME, 'select' ); SELECT policy_roles_are( 'public', 'tap_table', 'tap_policy', ARRAY [ 'tap_user_1', -- 檢測使用者 "tap_user_1" 中是否為RLS策略 "tap_policy" 限制使用者 'tap_user_2' -- 檢測使用者 "tap_user_2" 中是否為RLS策略 "tap_policy" 限制使用者 ] ); SELECT policies_are( 'public', 'tap_table', ARRAY [ 'tap_policy' -- 檢測表 "tap_policy" 中是否存在RLS策略 "tap_policy" ] ); SELECT * FROM check_test( policy_roles_are( 'public', 'tap_table', 'tap_policy', ARRAY [ 'tap_user_1' -- 檢測使用者 "tap_user_1" 中是否為RLS策略 "tap_policy" 限制的所有使用者 ]), false, 'check policy roles', 'Policy tap_policy for table public.tap_table should have the correct roles'); SELECT * FROM finish(); ROLLBACK; DROP POLICY tap_policy ON tap_table; DROP TABLE tap_table; DROP USER tap_user_1; DROP USER tap_user_2;說明其中:
policy_cmd_is用於檢測RLS策略應用的語句類型。policy_roles_are用於檢測RLS策略是否應用於其指定的所有使用者。若且唯若其中指定了該RLS策略應用的所有使用者,函數才會返回TRUE。policies_are用於檢測表中是否含有某RLS策略。
為了測試預期內的錯誤結果時,可以使用
check_test方法指定預期結果為TRUE或FALSE。上述測試將返回如下結果:policy_cmd_is ------------------------------------------------------------------------------------ ok 1 - Policy tap_policy for table public.tap_table should apply to SELECT command (1 row) policy_roles_are ----------------------------------------------------------------------------------- ok 2 - Policy tap_policy for table public.tap_table should have the correct roles (1 row) policies_are ---------------------------------------------------------------- ok 3 - Table public.tap_table should have the correct policies (1 row) check_test -------------------------------------------------------------- ok 4 - check policy roles should fail ok 5 - check policy roles should have the proper description (2 rows) finish -------- (0 rows)列測試
如果需要檢測表中的某列是否存在以及該列是否為主鍵/外鍵等內容,可以參考以下測試樣本:
CREATE TABLE tap_table(col INT PRIMARY KEY, tap_desc TEXT); CREATE INDEX tap_table_index ON tap_table(col); CREATE UNIQUE INDEX tap_table_unique_index ON tap_table(col); BEGIN; SELECT plan(7); -- 測試 "col" 列是否為 "tap_table" 表的主鍵 SELECT col_is_pk('tap_table', 'col'); -- 測試 "tap_desc" 列是否為 "tap_table" 表的非主鍵列 SELECT col_isnt_pk('tap_table', 'tap_desc'); -- 測試 "col" 列是否為 "tap_table" 表的外鍵 SELECT * FROM check_test( col_is_fk('tap_table', 'col'), false, 'check foreign key of table', 'Column tap_table(col) should be a foreign key'); -- 測試 "col" 列是否為 "tap_table" 表的非外鍵列 SELECT col_isnt_fk('tap_table', 'col'); -- 測試 "col" 列是否存在於 "tap_table" 表中 SELECT has_column('tap_table', 'col'); -- 測試 "non_col" 列是否不存在於 "tap_table" 表中 SELECT hasnt_column('tap_table', 'non_col'); SELECT * FROM finish(); ROLLBACK; DROP TABLE tap_table;說明其中:
col_is_pk用於檢測某列是否為表的主鍵列。col_isnt_pk用於檢測某列是否為表的非主鍵列。col_isnt_fk用於檢測某列是否為表的非外鍵列。has_column用於檢測表中是否含有某列。hasnt_column用於檢測表中是否不含有某列。
上述測試將返回如下結果:
col_is_pk ------------------------------------------------------ ok 1 - Column tap_table(col) should be a primary key (1 row) col_isnt_pk --------------------------------------------------------------- ok 2 - Column tap_table(tap_desc) should not be a primary key (1 row) check_test ---------------------------------------------------------------------- ok 3 - check foreign key of table should fail ok 4 - check foreign key of table should have the proper description (2 rows) col_isnt_fk ---------------------------------------------------------- ok 5 - Column tap_table(col) should not be a foreign key (1 row) has_column ------------------------------------------ ok 6 - Column tap_table.col should exist (1 row) hasnt_column -------------------------------------------------- ok 7 - Column tap_table.non_col should not exist (1 row) finish -------- (0 rows)函數測試
如果需要對函數的傳回型別、是否為
SECURITY DEFINER進行檢查,可以參考以下測試樣本:CREATE OR REPLACE FUNCTION tap_function() RETURNS text AS $$ BEGIN RETURN 'This is tap test function'; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE OR REPLACE FUNCTION tap_function_bool(arg1 integer, arg2 boolean, arg3 text) RETURNS boolean AS $$ BEGIN RETURN true; END; $$ LANGUAGE plpgsql; BEGIN; SELECT plan(6); -- 檢測函數 "tap_function" 傳回型別是否為 "text" SELECT function_returns('tap_function', 'text'); -- 檢測參數列表為 'integer', 'boolean', 'text'的函數 "tap_function_bool" 傳回型別是否為 "boolean" SELECT function_returns('tap_function_bool', ARRAY['integer', 'boolean', 'text'], 'boolean'); -- 檢測函數 "tap_function" 是否為 "SECURITY DEFINER" SELECT is_definer('tap_function'); -- 檢測函數 "tap_function_bool" 是否為非 "SECURITY DEFINER" SELECT isnt_definer('tap_function_bool'); -- 檢測函數 "tap_function_bool" 是否為 "SECURITY DEFINER" SELECT * FROM check_test( is_definer('tap_function_bool'), false, 'check function security definer', 'Function tap_function_bool() should be security definer'); SELECT * FROM finish(); ROLLBACK; DROP FUNCTION tap_function; DROP FUNCTION tap_function_bool;說明其中:
function_returns用於檢測函數傳回型別是否為指定類型,同時可以指定該函數的參數列表。is_definer方法用於檢測指定函數是否為SECURITY DEFINER。isnt_definer方法用於檢測指定函數是否為非SECURITY DEFINER。
上述測試樣本將返回以下結果:
function_returns --------------------------------------------------- ok 1 - Function tap_function() should return text (1 row) function_returns --------------------------------------------------------------------------------- ok 2 - Function tap_function_bool(integer, boolean, text) should return boolean (1 row) is_definer ----------------------------------------------------------- ok 3 - Function tap_function() should be security definer (1 row) isnt_definer -------------------------------------------------------------------- ok 4 - Function tap_function_bool() should not be security definer (1 row) check_test --------------------------------------------------------------------------- ok 5 - check function security definer should fail ok 6 - check function security definer should have the proper description (2 rows) finish -------- (0 rows)其他
pgtap外掛程式提供了包括上述測試在內豐富的測試方法,雖然測試內容不同,但在整體使用方式上與上述樣本一致。如需擷取更多資訊,請參見pgtap官方文檔。