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官方文档。