全部產品
Search
文件中心

PolarDB:pgtap(單元測試)

更新時間:Nov 07, 2024

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_tablehasnt_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方法指定預期結果為TRUEFALSE。上述測試將返回如下結果:

                                       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官方文檔