{"id":37854,"date":"2025-08-29T18:23:47","date_gmt":"2025-08-29T16:23:47","guid":{"rendered":"https:\/\/www.dbi-services.com\/blog\/?p=37854"},"modified":"2025-08-29T18:23:50","modified_gmt":"2025-08-29T16:23:50","slug":"unit-testing-postgresql-with-pgtap","status":"publish","type":"post","link":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/","title":{"rendered":"Unit Testing PostgreSQL with pgTAP"},"content":{"rendered":"<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"800\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image-2.png\" alt=\"\" class=\"wp-image-37881\" style=\"width:412px;height:auto\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image-2.png 800w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image-2-300x300.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image-2-150x150.png 150w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image-2-768x768.png 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-introduction\">Introduction<\/h2>\n\n\n\n<p>Unit testing is a fundamental practice in software development, ensuring that individual components function correctly. When working with PostgreSQL, testing database logic\u2014such as functions, triggers, and constraints\u2014is crucial for maintaining data integrity and reliability. One powerful tool for this purpose is <strong>pgTAP<\/strong>.<br>pgTAP is a PostgreSQL extension that provides a set of TAP (Test Anything Protocol) functions for writing unit tests directly in SQL. It allows developers to test database functions, schemas, constraints, and much more in an automated and repeatable way.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-installing-pgtap\">Installing pgTAP<\/h2>\n\n\n\n<p>Before using pgTAP, you need to install it on your PostgreSQL instance. You can install it from source as follows:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n10:18:56 postgres@ws-pgdev:\/home\/postgres\/ &#x5B;sw] wget https:\/\/api.pgxn.org\/dist\/pgtap\/1.3.3\/pgtap-1.3.3.zip .\n--2025-04-02 10:19:53--  https:\/\/api.pgxn.org\/dist\/pgtap\/1.3.3\/pgtap-1.3.3.zip\nResolving api.pgxn.org (api.pgxn.org)... 88.198.49.178\nConnecting to api.pgxn.org (api.pgxn.org)|88.198.49.178|:443... connected.\nHTTP request sent, awaiting response... 200 OK\nLength: 329966 (322K) &#x5B;application\/zip]\nSaving to: \u2018pgtap-1.3.3.zip\u2019\n\npgtap-1.3.3.zip                         100%&#x5B;============================================================================&gt;] 322.23K  --.-KB\/s    in 0.1s\n\n2025-04-02 10:19:54 (3.18 MB\/s) - \u2018pgtap-1.3.3.zip\u2019 saved &#x5B;329966\/329966]\n\n--2025-04-02 10:19:54--  http:\/\/.\/\nResolving . (.)... failed: No address associated with hostname.\nwget: unable to resolve host address \u2018.\u2019\nFINISHED --2025-04-02 10:19:54--\nTotal wall clock time: 0.4s\nDownloaded: 1 files, 322K in 0.1s (3.18 MB\/s)\n\n10:19:54 postgres@ws-pgdev:\/home\/postgres\/ &#x5B;sw] unzip pgtap-1.3.3.zip\nArchive:  pgtap-1.3.3.zip\nb941782fada240afdb7057065eb3261a21e8512c\n   creating: pgtap-1.3.3\/\n  inflating: pgtap-1.3.3\/Changes\n...\n...\n\n10:20:11 postgres@ws-pgdev:\/home\/postgres\/ &#x5B;sw] cd pgtap-1.3.3\/\n11:11:58 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] make\n\nGNUmake running against Postgres version 17.0, with pg_config located at \/u01\/app\/postgres\/product\/17\/db_0\/bin\n\nMakefile:186: To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module\nMakefile:187: must be installed from CPAN. To do so, simply run:\nMakefile:188: cpan TAP::Parser::SourceHandler::pgTAP\ncp sql\/pgtap--0.95.0--0.96.0.sql.in sql\/pgtap--0.95.0--0.96.0.sql\ncp sql\/pgtap--0.96.0--0.97.0.sql.in sql\/pgtap--0.96.0--0.97.0.sql\ncp sql\/pgtap--0.97.0--0.98.0.sql.in sql\/pgtap--0.97.0--0.98.0.sql\ncp sql\/pgtap--0.98.0--0.99.0.sql.in sql\/pgtap--0.98.0--0.99.0.sql\ncp sql\/pgtap--0.99.0--1.0.0.sql.in sql\/pgtap--0.99.0--1.0.0.sql\ncp sql\/pgtap.sql.in sql\/pgtap.sql\nsed -e &#039;s,MODULE_PATHNAME,$libdir\/pgtap,g&#039; -e &#039;s,__OS__,linux,g&#039; -e &#039;s,__VERSION__,1.3,g&#039; sql\/pgtap.sql &gt; sql\/pgtap.tmp\nmv sql\/pgtap.tmp sql\/pgtap.sql\n&#039;\/usr\/bin\/perl&#039; -e &#039;for (grep { \/^CREATE \/} reverse &lt;&gt;) { chomp; s\/CREATE (OR REPLACE )?\/DROP \/; s\/DROP (FUNCTION|VIEW|TYPE) \/DROP $1 IF EXISTS \/; s\/ (DEFAUL                                T|=)&#x5B; ]+&#x5B;a-zA-Z0-9]+\/\/g; print &quot;$_;\\n&quot; }&#039; sql\/pgtap.sql &gt; sql\/uninstall_pgtap.sql\ncp sql\/pgtap.sql.in sql\/pgtap-static.sql.tmp\n\n*** Patching pgtap-static.sql with compat\/install-9.6.patch\npatching file sql\/pgtap-static.sql.tmp\n\n*** Patching pgtap-static.sql with compat\/install-9.4.patch\npatching file sql\/pgtap-static.sql.tmp\n\n*** Patching pgtap-static.sql with compat\/install-9.2.patch\npatching file sql\/pgtap-static.sql.tmp\n\n*** Patching pgtap-static.sql with compat\/install-9.1.patch\npatching file sql\/pgtap-static.sql.tmp\nsed -e &#039;s#MODULE_PATHNAME#$libdir\/pgtap#g&#039; -e &#039;s#__OS__#linux#g&#039; -e &#039;s#__VERSION__#1.3#g&#039; sql\/pgtap-static.sql.tmp &gt; sql\/pgtap-static.sql\n&#039;\/usr\/bin\/perl&#039; compat\/gencore 0 sql\/pgtap-static.sql &gt; sql\/pgtap-core.sql\n&#039;\/usr\/bin\/perl&#039; compat\/gencore 1 sql\/pgtap-static.sql &gt; sql\/pgtap-schema.sql\ncp sql\/pgtap.sql sql\/pgtap--1.3.3.sql\ncp sql\/pgtap-core.sql sql\/pgtap-core--1.3.3.sql\ncp sql\/pgtap-schema.sql sql\/pgtap-schema--1.3.3.sql\n\n11:12:02 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] make install\n\nGNUmake running against Postgres version 17.0, with pg_config located at \/u01\/app\/postgres\/product\/17\/db_0\/bin\n\nMakefile:186: To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module\nMakefile:187: must be installed from CPAN. To do so, simply run:\nMakefile:188: cpan TAP::Parser::SourceHandler::pgTAP\nmkdir -p &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/extension&#039;\nmkdir -p &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/extension&#039;\nmkdir -p &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/doc\/extension&#039;\n\/bin\/sh \/u01\/app\/postgres\/product\/17\/db_0\/lib\/pgxs\/src\/makefiles\/..\/..\/config\/install-sh -c -m 644 .\/\/pgtap.control &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/extension\/&#039;\n\/bin\/sh \/u01\/app\/postgres\/product\/17\/db_0\/lib\/pgxs\/src\/makefiles\/..\/..\/config\/install-sh -c -m 644 .\/\/sql\/pgtap--0.90.0--0.91.0.sql .\/\/sql\/pgtap--0.91.0--0.92.0.sql .\/\/sql\/pgtap--0.92.0--0.93.0.sql .\/\/sql\/pgtap--0.93.0--0.94.0.sql .\/\/sql\/pgtap--0.94.0--0.95.0.sql .\/\/sql\/pgtap--0.95.0--0.96.0.sql .\/\/sql\/pgtap--0.96.0--0.97.0.sql .\/\/sql\/pgtap--0.97.0--0.98.0.sql .\/\/sql\/pgtap--0.98.0--0.99.0.sql .\/\/sql\/pgtap--0.99.0--1.0.0.sql .\/\/sql\/pgtap--1.0.0--1.1.0.sql .\/\/sql\/pgtap--1.1.0--1.2.0.sql .\/\/sql\/pgtap--1.2.0--1.3.0.sql .\/\/sql\/pgtap--1.3.0--1.3.1.sql .\/\/sql\/pgtap--1.3.1--1.3.2.sql .\/\/sql\/pgtap--1.3.2--1.3.3.sql .\/\/sql\/pgtap--1.3.3.sql .\/\/sql\/pgtap--unpackaged--0.91.0.sql .\/\/sql\/pgtap-core--1.3.3.sql .\/\/sql\/pgtap-core.sql .\/\/sql\/pgtap-schema--1.3.3.sql .\/\/sql\/pgtap-schema.sql .\/\/sql\/pgtap.sql .\/\/sql\/uninstall_pgtap.sql  &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/extension\/&#039;\n\/bin\/sh \/u01\/app\/postgres\/product\/17\/db_0\/lib\/pgxs\/src\/makefiles\/..\/..\/config\/install-sh -c -m 644 .\/\/doc\/pgtap.mmd &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/doc\/extension\/&#039;\n<\/pre><\/div>\n\n\n<p>As mentioned in the output of the previous command, we need to run some commands to be able to use pg_prove, which we are going to use later:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n11:14:23 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] cpan TAP::Parser::SourceHandler::pgTAP\nLoading internal logger. Log::Log4perl recommended for better logging\n\nCPAN.pm requires configuration, but most of it can be done automatically.\nIf you answer &#039;no&#039; below, you will enter an interactive dialog for each\nconfiguration option instead.\n\nWould you like to configure as much as possible automatically? &#x5B;yes] yes\n\nWarning: You do not have write permission for Perl library directories.\n\nTo install modules, you need to configure a local Perl library directory or\nescalate your privileges.  CPAN can help you by bootstrapping the local::lib\nmodule or by configuring itself to use &#039;sudo&#039; (if available).  You may also\nresolve this problem manually if you need to customize your setup.\n\nWhat approach do you want?  (Choose &#039;local::lib&#039;, &#039;sudo&#039; or &#039;manual&#039;)\n &#x5B;local::lib] sudo\nFetching with HTTP::Tiny:\nhttps:\/\/cpan.org\/authors\/01mailrc.txt.gz\nReading &#039;\/home\/postgres\/.cpan\/sources\/authors\/01mailrc.txt.gz&#039;\n............................................................................DONE\nFetching with HTTP::Tiny:\nhttps:\/\/cpan.org\/modules\/02packages.details.txt.gz\nReading &#039;\/home\/postgres\/.cpan\/sources\/modules\/02packages.details.txt.gz&#039;\n  Database was generated on Wed, 02 Apr 2025 08:29:02 GMT\n..............\n  New CPAN.pm version (v2.38) available.\n  &#x5B;Currently running version is v2.33]\n  You might want to try\n    install CPAN\n    reload cpan\n  to both upgrade CPAN.pm and run the new version without leaving\n  the current session.\n\n\n..............................................................DONE\nFetching with HTTP::Tiny:\nhttps:\/\/cpan.org\/modules\/03modlist.data.gz\nReading &#039;\/home\/postgres\/.cpan\/sources\/modules\/03modlist.data.gz&#039;\nDONE\nWriting \/home\/postgres\/.cpan\/Metadata\nRunning install for module &#039;TAP::Parser::SourceHandler::pgTAP&#039;\nFetching with HTTP::Tiny:\nhttps:\/\/cpan.org\/authors\/id\/D\/DW\/DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz\nFetching with HTTP::Tiny:\nhttps:\/\/cpan.org\/authors\/id\/D\/DW\/DWHEELER\/CHECKSUMS\nChecksum for \/home\/postgres\/.cpan\/sources\/authors\/id\/D\/DW\/DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz ok\n&#039;YAML&#039; not installed, will not store persistent state\nConfiguring D\/DW\/DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz with Build.PL\nCreated MYMETA.yml and MYMETA.json\nCreating new &#039;Build&#039; script for &#039;TAP-Parser-SourceHandler-pgTAP&#039; version &#039;3.37&#039;\n  DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz\n  \/usr\/bin\/perl Build.PL --installdirs site -- OK\nRunning Build for D\/DW\/DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz\nBuilding TAP-Parser-SourceHandler-pgTAP\n  DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz\n  .\/Build -- OK\nRunning Build test for DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz\nt\/source_handler.t .. ok\nAll tests successful.\nFiles=1, Tests=47,  1 wallclock secs ( 0.03 usr  0.00 sys +  0.08 cusr  0.12 csys =  0.23 CPU)\nResult: PASS\n  DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz\n  .\/Build test -- OK\nRunning Build install for DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz\nBuilding TAP-Parser-SourceHandler-pgTAP\nInstalling \/usr\/local\/man\/man1\/pg_prove.1p\nInstalling \/usr\/local\/man\/man1\/pg_tapgen.1p\nInstalling \/usr\/local\/share\/perl\/5.36.0\/TAP\/Parser\/SourceHandler\/pgTAP.pm\nInstalling \/usr\/local\/man\/man3\/TAP::Parser::SourceHandler::pgTAP.3pm\nInstalling \/usr\/local\/bin\/pg_tapgen\nInstalling \/usr\/local\/bin\/pg_prove\n  DWHEELER\/TAP-Parser-SourceHandler-pgTAP-3.37.tar.gz\n  sudo .\/Build install  -- OK\n11:15:03 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] mkdir -p &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/extension&#039;\n11:15:05 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] mkdir -p &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/extension&#039;\nmkdir -p &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/doc\/extension&#039;\n11:15:10 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] \/bin\/sh \/u01\/app\/postgres\/product\/17\/db_0\/lib\/pgxs\/src\/makefiles\/..\/..\/config\/install-sh -c -m 64                                4 .\/\/pgtap.control &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/extension\/&#039;\n11:15:18 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] \/bin\/sh \/u01\/app\/postgres\/product\/17\/db_0\/lib\/pgxs\/src\/makefiles\/..\/..\/config\/install-sh -c -m 64                                4 .\/\/sql\/pgtap--0.90.0--0.91.0.sql .\/\/sql\/pgtap--0.91.0--0.92.0.sql .\/\/sql\/pgtap--0.92.0--0.93.0.sql .\/\/sql\/pgtap--0.93.0--0.94.0.sql .\/\/sql\/pgtap--0.94.0--0                                .95.0.sql .\/\/sql\/pgtap--0.95.0--0.96.0.sql .\/\/sql\/pgtap--0.96.0--0.97.0.sql .\/\/sql\/pgtap--0.97.0--0.98.0.sql .\/\/sql\/pgtap--0.98.0--0.99.0.sql .\/\/sql\/pgtap--0                                .99.0--1.0.0.sql .\/\/sql\/pgtap--1.0.0--1.1.0.sql .\/\/sql\/pgtap--1.1.0--1.2.0.sql .\/\/sql\/pgtap--1.2.0--1.3.0.sql .\/\/sql\/pgtap--1.3.0--1.3.1.sql .\/\/sql\/pgtap--1.                                3.1--1.3.2.sql .\/\/sql\/pgtap--1.3.2--1.3.3.sql .\/\/sql\/pgtap--1.3.3.sql .\/\/sql\/pgtap--unpackaged--0.91.0.sql .\/\/sql\/pgtap-core--1.3.3.sql .\/\/sql\/pgtap-core.sql                                 .\/\/sql\/pgtap-schema--1.3.3.sql .\/\/sql\/pgtap-schema.sql .\/\/sql\/pgtap.sql .\/\/sql\/uninstall_pgtap.sql  &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/extension\/&#039;\n11:15:33 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] \/bin\/sh \/u01\/app\/postgres\/product\/17\/db_0\/lib\/pgxs\/src\/makefiles\/..\/..\/config\/install-sh -c -m 64                                4 .\/\/doc\/pgtap.mmd &#039;\/u01\/app\/postgres\/product\/17\/db_0\/share\/doc\/extension\/&#039;\n11:15:37 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] pg_prove\nNo tests named and &#039;t&#039; directory not found at \/usr\/share\/perl\/5.36\/App\/Prove.pm line 522.\n\n11:15:42 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] pg_prove --version\npg_prove 3.37\n<\/pre><\/div>\n\n\n<p>You can check if pgTAP was installed properly using the following command:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n10:24:09 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] make installcheck\n\nGNUmake running against Postgres version 17.0, with pg_config located at \/u01\/app\/postgres\/product\/17\/db_0\/bin\n\nMakefile:186: To use pg_prove, TAP::Parser::SourceHandler::pgTAP Perl module\nMakefile:187: must be installed from CPAN. To do so, simply run:\nMakefile:188: cpan TAP::Parser::SourceHandler::pgTAP\nUsing 89 parallel test connections\nRebuilding test\/build\/all_tests\nSchedule changed to test\/build\/parallel.sch\ncp `cat test\/build\/which_schedule` test\/build\/run.sch\necho &quot;# +++ regress install-check in  +++&quot; &amp;&amp; \/u01\/app\/postgres\/product\/17\/db_0\/lib\/pgxs\/src\/makefiles\/..\/..\/src\/test\/regress\/pg_regress --inputdir=.\/ --bindir=&#039;\/u01\/app\/postgres\/product\/17\/db_0\/bin&#039;    --inputdir=test --max-connections=89 --schedule test\/schedule\/main.sch   --schedule test\/build\/run.sch\n# +++ regress install-check in  +++\n# using postmaster on Unix socket, port 5432\nok 1         - build                                     369 ms\n...\nok 4         - hastap                                   1309 ms\n# parallel group (35 tests):  matching istap do_tap moretap util performs_ok performs_within todotap check cmpok pg73 runjusttests roletap throwtap trigger usergroup enumtap policy runtests runnotests proctap fktap privs inheritance partitions valueset functap resultset aretap extension ownership ruletap pktap index unique\nok 5         + aretap                                   5911 ms\nok 6         + check                                    1558 ms\n...\nok 39        + valueset                                 3784 ms\n1..39\n# All 39 tests passed.\n<\/pre><\/div>\n\n\n<p>Once installed, enable it in your database:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n10:25:06 postgres@ws-pgdev:\/home\/postgres\/pgtap-1.3.3\/ &#x5B;sw] psql\npsql (17.0 dbi services build)\nType &quot;help&quot; for help.\n\npostgres=# \\c hybrid\nYou are now connected to database &quot;hybrid&quot; as user &quot;postgres&quot;.\nhybrid=# CREATE EXTENSION pgtap;\nCREATE EXTENSION\nhybrid=# \\dx\n                                                        List of installed extensions\n        Name        | Version |    Schema    |                                          Description\n--------------------+---------+--------------+-----------------------------------------------------------------------------------------------\n btree_gist         | 1.7     | training_app | support for indexing common datatypes in GiST\n orafce             | 4.14    | training_app | Functions and operators that emulate a subset of functions and packages from the Oracle RDBMS\n pg_stat_statements | 1.11    | public       | track planning and execution statistics of all SQL statements executed\n pg_trgm            | 1.6     | training_app | text similarity measurement and index searching based on trigrams\n pgcrypto           | 1.3     | training_app | cryptographic functions\n pgtap              | 1.3.3   | training_app | Unit testing for PostgreSQL\n plperl             | 1.0     | pg_catalog   | PL\/Perl procedural language\n plpgsql            | 1.0     | pg_catalog   | PL\/pgSQL procedural language\n(8 rows)\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-writing-your-first-pgtap-tests\">Writing Your First pgTAP Tests<\/h2>\n\n\n\n<p>pgTAP provides a wide range of assertions for testing various database objects. Let&#8217;s go through some examples.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-1-testing-a-function\">1. Testing a Function<\/h3>\n\n\n\n<p>Assume we have a function that verifies a password based on a specific pattern:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE OR REPLACE FUNCTION training_app.f_password_verify (pv_password TEXT) RETURNS BOOLEAN\nAS $$\n  SELECT pv_password ~ &#039;^(?=.{10,}$)(?=.*&#x5B;a-z])(?=.*&#x5B;A-Z])(?=.*&#x5B;0-9])(?=.*\\W).*$&#039;;\n$$ LANGUAGE sql;\n\nhybrid=# \\df f_password_verify\n                                List of functions\n    Schema    |       Name        | Result data type | Argument data types | Type\n--------------+-------------------+------------------+---------------------+------\n training_app | f_password_verify | boolean          | pv_password text    | func\n(1 row)\n\n<\/pre><\/div>\n\n\n<p>To test this function with pgTAP:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nhybrid=# SELECT plan(4);\n plan\n------\n 1..4\n(1 row)\n\nhybrid=# SELECT ok(training_app.f_password_verify(&#039;ValidPass1!&#039;), &#039;Valid password should return true&#039;);\n                    ok\n------------------------------------------\n ok 1 - Valid password should return true\n(1 row)\n\nhybrid=# SELECT ok(NOT training_app.f_password_verify(&#039;short1!&#039;), &#039;Too short password should return false&#039;);\n                      ok\n-----------------------------------------------\n ok 2 - Too short password should return false\n(1 row)\n\nhybrid=# SELECT ok(NOT training_app.f_password_verify(&#039;NoNumberPass!&#039;), &#039;Password without a number should return false&#039;);\n                          ok\n------------------------------------------------------\n ok 3 - Password without a number should return false\n(1 row)\n\nhybrid=# SELECT ok(NOT training_app.f_password_verify(&#039;NoSpecialChar1&#039;), &#039;Password without special character should return false&#039;);\n                              ok\n---------------------------------------------------------------\n ok 4 - Password without special character should return false\n(1 row)\n\nhybrid=# SELECT * FROM finish();\n finish\n--------\n(0 rows)\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-2-testing-table-constraints\">2. Testing Table Constraints<\/h3>\n\n\n\n<p>Consider the <code>users<\/code> table with the following schema:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nhybrid=# \\d users\n                         Table &quot;training_app.users&quot;\n  Column  |   Type    | Collation | Nullable |           Default\n----------+-----------+-----------+----------+------------------------------\n id       | integer   |           | not null | generated always as identity\n username | text      |           | not null |\n password | text      |           | not null |\n created  | date      |           |          | now()\n validity | tstzrange |           |          |\nIndexes:\n    &quot;users_pkey&quot; PRIMARY KEY, btree (id)\n    &quot;i_username_trgm&quot; gin (username gin_trgm_ops)\n    &quot;i_users_username&quot; btree (username)\n    &quot;i_users_username_btree_partial&quot; btree (created) WHERE created &gt;= &#039;2024-11-07&#039;::date AND created &lt; &#039;2024-11-08&#039;::date\nCheck constraints:\n    &quot;user_check_username&quot; CHECK (username ~* &#039;&#x5B;A-Z0-9._%+-]+@&#x5B;A-Z0-9.-]+\\.&#x5B;A-Z]{2,4}&#039;::text)\n    &quot;user_check_username_length&quot; CHECK (length(username) &lt;= 72)\nReferenced by:\n    TABLE &quot;user_training&quot; CONSTRAINT &quot;fk_user_training_users&quot; FOREIGN KEY (user_id) REFERENCES users(id)\n    TABLE &quot;users_history&quot; CONSTRAINT &quot;fk_users_history_user_id_users_id&quot; FOREIGN KEY (user_id) REFERENCES users(id)\nPolicies:\n    POLICY &quot;policy_current_month&quot; FOR SELECT\n      TO role_app_read_only\n      USING (((EXTRACT(month FROM created))::integer = (EXTRACT(month FROM now()))::integer))\nTriggers:\n    t_log_user_history BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION tf_user_history()\n\ncreate or replace function training_app.tf_user_history() returns trigger as\n$$\nbegin\n   insert into training_app.users_history ( user_id, username, password, created, validity)\n                                  values ( old.id, old.username, old.password, old.created, old.validity);\n   return new;\nend;                            \n$$ language plpgsql;\n\ncreate trigger t_log_user_history\n   before update on training_app.users\n   for each row\n   execute procedure training_app.tf_user_history();\n<\/pre><\/div>\n\n\n<p>To test the constraints, create a test file <code>test_users_constraints.sql<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n10:57:07 postgres@ws-pgdev:\/u01\/app\/postgres\/local\/dmk\/tests\/ &#x5B;sw] touch test_users_constraints.sql\n10:57:37 postgres@ws-pgdev:\/u01\/app\/postgres\/local\/dmk\/tests\/ &#x5B;sw] cat test_users_constraints.sql\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nBEGIN;\nSELECT plan(2);\n\n-- Test CHECK constraint on username format\nSELECT throws_like(\n  $$INSERT INTO training_app.users (username, password) VALUES (&#039;invalid_user&#039;, &#039;Password1!&#039;)$$,\n  &#039;new row for relation &quot;users&quot; violates check constraint &quot;user_check_username&quot;&#039;,\n  &#039;Invalid username should fail CHECK constraint&#039;\n);\n\n-- Test CHECK constraint on username length\nSELECT throws_like(\n  $$INSERT INTO training_app.users (username, password) VALUES (repeat(&#039;a&#039;, 73), &#039;Password1!&#039;)$$,\n  &#039;new row for relation &quot;users&quot; violates check constraint &quot;user_check_username&quot;&#039;,\n  &#039;Username exceeding 72 characters should fail CHECK constraint&#039;\n);\n\nSELECT * FROM finish();\nROLLBACK;\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-running-tests\">Running Tests<\/h3>\n\n\n\n<p>You can execute pgTAP tests using <code>pg_prove<\/code>, a command-line tool for running TAP tests. We are now going to test it with the file we just created, <em>test_users_constraints.sql<\/em>.<\/p>\n\n\n\n<p>Run it with <code>pg_prove<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n11:45:14 postgres@ws-pgdev:\/u01\/app\/postgres\/local\/dmk\/tests\/ &#x5B;sw] pg_prove -d hybrid -U postgres -p 5432 test_users_constraints.sql\ntest_users_constraints.sql .. ok\nAll tests successful.\nFiles=1, Tests=2,  0 wallclock secs ( 0.05 usr  0.01 sys +  0.00 cusr  0.01 csys =  0.07 CPU)\nResult: PASS\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-3-testing-triggers\">3. Testing Triggers<\/h3>\n\n\n\n<p>To verify that our trigger correctly logs changes to the <code>users<\/code> table, we check:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>That the recorded historical data correctly reflects the old values before the update.<\/li>\n\n\n\n<li>That an update on <code>users<\/code> triggers an insert into <code>users_history<\/code>.<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n13:43:55 postgres@ws-pgdev:\/u01\/app\/postgres\/local\/dmk\/tests\/ &#x5B;sw] cat test_trigger.sql\nBEGIN;\nSELECT plan(2);\n\n-- Insert a test user with a valid email as username\nINSERT INTO training_app.users (username, password) VALUES (&#039;testuser@example.com&#039;, &#039;TestPassword123!&#039;);\n\n-- Update the user&#039;s username (this should activate the trigger)\nUPDATE training_app.users SET username = &#039;updateduser@example.com&#039; WHERE username = &#039;testuser@example.com&#039;;\n\n-- Check if the corresponding row is added to the users_history table\nSELECT ok(\n    (SELECT COUNT(*) FROM training_app.users_history WHERE user_id = (SELECT id FROM training_app.users WHERE username = &#039;updateduser@example.com&#039;)) &gt; 0,\n    &#039;User history should be logged in users_history after update&#039;\n);\n\n-- Check if the values in users_history match the old values (before the update)\nSELECT is(\n    (SELECT username FROM training_app.users_history WHERE user_id = (SELECT id FROM training_app.users WHERE username = &#039;updateduser@example.com&#039;) ORDER BY created DESC LIMIT 1),\n    &#039;testuser@example.com&#039;,\n    &#039;Username in user history should match the old (pre-update) value&#039;\n);\n\nSELECT * FROM finish();\nROLLBACK;\n<\/pre><\/div>\n\n\n<p>Execute the test using:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n13:58:38 postgres@ws-pgdev:\/u01\/app\/postgres\/local\/dmk\/tests\/ &#x5B;sw] pg_prove -d hybrid -U postgres -p 5432 test_trigger.sql\ntest_trigger.sql .. ok\nAll tests successful.\nFiles=1, Tests=2,  0 wallclock secs ( 0.04 usr  0.01 sys +  0.01 cusr  0.01 csys =  0.07 CPU)\nResult: PASS\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-benefits-of-using-pgtap\">Benefits of Using pgTAP<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Automated Testing<\/strong>: Helps maintain database integrity by catching errors early.<\/li>\n\n\n\n<li><strong>SQL-Based<\/strong>: No need for external scripting languages; tests are written in SQL.<\/li>\n\n\n\n<li><strong>Integration with CI\/CD<\/strong>: Works with CI\/CD pipelines to ensure database quality.<\/li>\n\n\n\n<li><strong>Comprehensive Assertions<\/strong>: Supports functions, constraints, indexes, views, and more.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion<\/h2>\n\n\n\n<p>pgTAP is a powerful tool for unit testing PostgreSQL databases. By incorporating it into your workflow, you can ensure that your database logic remains robust and reliable over time. Whether you&#8217;re testing functions, constraints, or triggers, pgTAP provides a structured and repeatable approach to database testing. You can find more information about pgTAP on the <a href=\"https:\/\/pgtap.org\/\">official website<\/a>.<br>Do you use pgTAP in your projects? Let me know in the comments how it has helped you!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Unit testing is a fundamental practice in software development, ensuring that individual components function correctly. When working with PostgreSQL, testing database logic\u2014such as functions, triggers, and constraints\u2014is crucial for maintaining data integrity and reliability. One powerful tool for this purpose is pgTAP.pgTAP is a PostgreSQL extension that provides a set of TAP (Test Anything [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":37879,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[229,198],"tags":[3005,3591,77,3590],"type_dbi":[3593,2749,3592],"class_list":["post-37854","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-database-administration-monitoring","category-database-management","tag-automated","tag-pgtap","tag-postgresql","tag-testing","type-automated","type-postgresql","type-testing"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.4) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Unit Testing PostgreSQL with pgTAP - dbi Blog<\/title>\n<meta name=\"description\" content=\"Learn how to perform unit testing in PostgreSQL using pgTAP. This guide covers installation, setup, and testidatabase functions, triggers, and constraints to ensure data integrity and reliability.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Unit Testing PostgreSQL with pgTAP\" \/>\n<meta property=\"og:description\" content=\"Learn how to perform unit testing in PostgreSQL using pgTAP. This guide covers installation, setup, and testidatabase functions, triggers, and constraints to ensure data integrity and reliability.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/\" \/>\n<meta property=\"og:site_name\" content=\"dbi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-08-29T16:23:47+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-08-29T16:23:50+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image.jpeg\" \/>\n\t<meta property=\"og:image:width\" content=\"800\" \/>\n\t<meta property=\"og:image:height\" content=\"533\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Joan Frey\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Joan Frey\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/\"},\"author\":{\"name\":\"Joan Frey\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/c03c47649664fe73b27ce457e99f5b06\"},\"headline\":\"Unit Testing PostgreSQL with pgTAP\",\"datePublished\":\"2025-08-29T16:23:47+00:00\",\"dateModified\":\"2025-08-29T16:23:50+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/\"},\"wordCount\":424,\"commentCount\":0,\"image\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/04\\\/image.jpeg\",\"keywords\":[\"automated\",\"pgTAP\",\"PostgreSQL\",\"testing\"],\"articleSection\":[\"Database Administration &amp; Monitoring\",\"Database management\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/\",\"name\":\"Unit Testing PostgreSQL with pgTAP - dbi Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/04\\\/image.jpeg\",\"datePublished\":\"2025-08-29T16:23:47+00:00\",\"dateModified\":\"2025-08-29T16:23:50+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/c03c47649664fe73b27ce457e99f5b06\"},\"description\":\"Learn how to perform unit testing in PostgreSQL using pgTAP. This guide covers installation, setup, and testidatabase functions, triggers, and constraints to ensure data integrity and reliability.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/04\\\/image.jpeg\",\"contentUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/04\\\/image.jpeg\",\"width\":800,\"height\":533,\"caption\":\"pgTAP_featured_image\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/unit-testing-postgresql-with-pgtap\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Unit Testing PostgreSQL with pgTAP\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/\",\"name\":\"dbi Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/c03c47649664fe73b27ce457e99f5b06\",\"name\":\"Joan Frey\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1e650cf665b4d44dd186355827c0b049d2f95c8cbb45fd10d4e7cb255be67ecb?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1e650cf665b4d44dd186355827c0b049d2f95c8cbb45fd10d4e7cb255be67ecb?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1e650cf665b4d44dd186355827c0b049d2f95c8cbb45fd10d4e7cb255be67ecb?s=96&d=mm&r=g\",\"caption\":\"Joan Frey\"},\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/author\\\/joanfrey\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Unit Testing PostgreSQL with pgTAP - dbi Blog","description":"Learn how to perform unit testing in PostgreSQL using pgTAP. This guide covers installation, setup, and testidatabase functions, triggers, and constraints to ensure data integrity and reliability.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/","og_locale":"en_US","og_type":"article","og_title":"Unit Testing PostgreSQL with pgTAP","og_description":"Learn how to perform unit testing in PostgreSQL using pgTAP. This guide covers installation, setup, and testidatabase functions, triggers, and constraints to ensure data integrity and reliability.","og_url":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/","og_site_name":"dbi Blog","article_published_time":"2025-08-29T16:23:47+00:00","article_modified_time":"2025-08-29T16:23:50+00:00","og_image":[{"width":800,"height":533,"url":"http:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image.jpeg","type":"image\/jpeg"}],"author":"Joan Frey","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Joan Frey"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/#article","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/"},"author":{"name":"Joan Frey","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/c03c47649664fe73b27ce457e99f5b06"},"headline":"Unit Testing PostgreSQL with pgTAP","datePublished":"2025-08-29T16:23:47+00:00","dateModified":"2025-08-29T16:23:50+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/"},"wordCount":424,"commentCount":0,"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image.jpeg","keywords":["automated","pgTAP","PostgreSQL","testing"],"articleSection":["Database Administration &amp; Monitoring","Database management"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/","url":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/","name":"Unit Testing PostgreSQL with pgTAP - dbi Blog","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/#primaryimage"},"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image.jpeg","datePublished":"2025-08-29T16:23:47+00:00","dateModified":"2025-08-29T16:23:50+00:00","author":{"@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/c03c47649664fe73b27ce457e99f5b06"},"description":"Learn how to perform unit testing in PostgreSQL using pgTAP. This guide covers installation, setup, and testidatabase functions, triggers, and constraints to ensure data integrity and reliability.","breadcrumb":{"@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/#primaryimage","url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image.jpeg","contentUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/04\/image.jpeg","width":800,"height":533,"caption":"pgTAP_featured_image"},{"@type":"BreadcrumbList","@id":"https:\/\/www.dbi-services.com\/blog\/unit-testing-postgresql-with-pgtap\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/www.dbi-services.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Unit Testing PostgreSQL with pgTAP"}]},{"@type":"WebSite","@id":"https:\/\/www.dbi-services.com\/blog\/#website","url":"https:\/\/www.dbi-services.com\/blog\/","name":"dbi Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/c03c47649664fe73b27ce457e99f5b06","name":"Joan Frey","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/1e650cf665b4d44dd186355827c0b049d2f95c8cbb45fd10d4e7cb255be67ecb?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/1e650cf665b4d44dd186355827c0b049d2f95c8cbb45fd10d4e7cb255be67ecb?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/1e650cf665b4d44dd186355827c0b049d2f95c8cbb45fd10d4e7cb255be67ecb?s=96&d=mm&r=g","caption":"Joan Frey"},"url":"https:\/\/www.dbi-services.com\/blog\/author\/joanfrey\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/37854","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/comments?post=37854"}],"version-history":[{"count":20,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/37854\/revisions"}],"predecessor-version":[{"id":37891,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/37854\/revisions\/37891"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media\/37879"}],"wp:attachment":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media?parent=37854"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/categories?post=37854"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/tags?post=37854"},{"taxonomy":"type","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/type_dbi?post=37854"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}