pgTap, tests unitaires pour PostgreSQL
Rodolphe Quiédeville
Meetup PostgreSQL Nantes
22 juin 2016
#mylife
Découvert Internet à 28.kbits avec Netscape Navigator
Utilise et produit du logiciel libre exclusivement
PostgreSQL depuis ... la 6.X ?
Consultant en performance des SI(G)
Data architect @PeopleDoc
Formateur Upstream University
Intro
suite de fonctions pour faciliter l’écriture de tests au
protocole TAP
écrit en Perl et PL/pgSQL
PostgreSQL 8.4
David E. Wheeler
BSD like
http://pgtap.org/
Tap
TAP, test anything protocol
Initialement écrit pour Perl (1987), avec des implémentations
aujourd’hui en C, C++, Python, PHP, Java, ....
1..4
ok 1 - Input file opened
not ok 2 - First line of the input valid
ok 3 - Read the rest of the file
not ok 4 - Summarized correctly # TODO Not written yet
Installation
# apt-get install pgtap
# apt-get install postgresql-9.1-pgtap
~# CREATE EXTENSION pgtap;
Installation
873 fonctions
2 vues
1 type composite
Fonctions
Test basique
SELECT ok ( 9 ^ 2 = 81 , ’ simple exponential ’ ) ;
Fonctions
Résultat de fonction
SELECT i s ( ultimate_answer ( ) , 42 , ’ Meaning of L i f e ’ ) ;
Fonctions
Résultat de requête
SELECT results_eq (
’SELECT ∗ FROM active_users ( ) ’ ,
’SELECT ∗ FROM users WHERE active ’ ,
’ active_users ( ) should return active users ’
) ;
Fonctions
Test de schéma
SELECT has_table ( ’myschema ’ : : name, ’ sometable ’ : : name) ;
Fonctions
Test de schéma
SELECT has_tablespace ( ’ sometablespace ’ , ’ / data / dbs ’ ) ;
Tests de schéma
has_table()
has_column()
has_relation()
has_type()
has_index()
has_composite()
has_trigger()
has_view()
has_fk()
Tests de schéma
hasnt_table()
hasnt_column()
hasnt_relation()
hasnt_type()
hasnt_index()
hasnt_composite()
hasnt_trigger()
hasnt_view()
hasnt_fk()
Tests de schéma
col_default_is()
col_is_fk()
col_is_null()
col_is_pk()
col_is_unique()
col_type_is()
Tests de schémas
col_default_is()
col_is_fk()
col_is_null()
col_is_pk()
col_is_unique()
col_type_is()
873 fonctions
Paramètres
Paramètres des fonctions
SELECT has_function (schema , function , args , description ) ;
SELECT has_function (schema , function , args ) ;
SELECT has_function (schema , function , description ) ;
SELECT has_function (schema , function ) ;
SELECT has_function ( function , args , description ) ;
SELECT has_function ( function , args ) ;
SELECT has_function ( function , description ) ;
SELECT has_function ( function ) ;
CAST everywhere
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+--------------+------------------+--------------------------+--------
public | has_function | text | name | normal
public | has_function | text | name, name | normal
public | has_function | text | name, name[] | normal
public | has_function | text | name, name, name[] | normal
public | has_function | text | name, name, name[], text | normal
public | has_function | text | name, name, text | normal
public | has_function | text | name, name[], text | normal
public | has_function | text | name, text | normal
(8 rows)
CAST everywhere
~# SELECT has_function ( ’ has_function ’ ) ;
has_function
---------------------------------------------
ok 8 - Function has_function() should exist
(1 row)
CAST everywhere
~# SELECT has_function ( ’ public ’ , ’ has_function ’ ) ;
has_function
---------------------------------
not ok 9 - has_function +
# Failed test 9: ‘‘has_function’’
(1 row)
CAST everywhere
~# SELECT has_function ( ’ public ’ : : name,
’ has_function ’ : : name) ;
has_function
-----------------------------------------------------
ok 10 - Function public.has_function() should exist
(1 row)
CAST everywhere
PREPARE count_site AS
SELECT count ( ∗ )
FROM s i t e
WHERE id < 0;
SELECT results_eq (
’ count_site ’ ,
ARRAY[ 0 ] ,
’ check s i t e name ’ ) ;
CAST everywhere
results_eq
-------------------------------------------------------------------
not ok 2 - check site name +
# Failed test 2: ‘‘check site name’’ +
# Number of columns or their types differ between the queries
(1 row)
CAST everywhere
~# df count
List of functions
Schema | Name | Result data type | Argument data types | Type
------------+-------+------------------+---------------------+------
pg_catalog | count | bigint | | agg
pg_catalog | count | bigint | "any" | agg
(2 rows)
CAST everywhere
SELECT results_eq (
’ count_site ’ ,
ARRAY[ 0 : : b i g i n t ] ,
’ check s i t e name ’ ) ;
Organisation
BEGIN;
SELECT plan ( 7 ) ;
SELECT has_table ( ’ domains ’ ) ;
SELECT has_table ( ’ s t u f f ’ ) ;
SELECT has_table ( ’ sources ’ ) ;
SELECT has_table ( ’ domain_stuff ’ ) ;
SELECT has_column ( ’ domains ’ , ’ id ’ ) ;
SELECT col_is_pk ( ’ domains ’ , ’ id ’ ) ;
SELECT has_column ( ’ domains ’ , ’ domain ’ ) ;
SELECT ∗ FROM f i n i s h ( ) ;
ROLLBACK;
Organisation
~∗# SELECT plan ( 7 ) ;
plan
−−−−−−
1..7
(1 row )
Organisation
~∗# SELECT ∗ FROM f i n i s h ( ) ;
f i n i s h
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Looks l i k e you f a i l e d 7 tests of 7
(1 row )
Erreur classique
BEGIN;
~∗# SELECT has_table ( ’ s i t e ’ ) ;
ERROR: P0001 : You t r i e d to run a t e s t without a plan !
Gotta have a
plan
CONTEXT: SQL statement ‘ ‘SELECT _get_latest ( ’ todo ’ ) ’ ’
PL /pgSQL function _todo ( ) l i n e 9 at assignment
SQL statement ‘ ‘SELECT _todo ( ) ’ ’
PL /pgSQL function ok ( boolean , t e x t ) l i n e 9 at assignment
LOCATION: exec_stmt_raise , pl_exec . c:3068
ROLLBACk;
Pas de plan
~# SELECT no_plan ( ) ;
no_plan
−−−−−−−−−
(0 rows )
~# SELECT has_table ( ’ s i t e ’ ) ;
has_table
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
ok 1 − Table s i t e should e x i s t
(1 row )
Cas classique
PREPARE create_site AS
INSERT INTO s i t e
( id , fqdn , sitename , uuid )
VALUES
(−1, ’www. foo . bar ’ , ’ foobar ’ , uuid_generate_v4 ( ) ) ,
(−2, ’www. biz .com ’ , ’ Bizcom ’ , uuid_generate_v4 ( ) ) ;
PREPARE check_site AS
SELECT sitename
FROM s i t e
WHERE id = −1;
SELECT lives_ok ( ’ create_site ’ , ’ [ SetUp ] create s i t e s ’ ) ;
SELECT results_eq ( ’ check_site ’ ,
ARRAY[ ’ foobar ’ : : t e x t ] ,
’ check s i t e name ’ ) ;
Utilisation
rodo@roz-desktop:~/meetNantes-3(master)$ pg_prove -d rodo tests/ -v
tests/tables.pg ..
1..2
ok 1 - Table items should exist
not ok 2 - Table bar should exist
# Failed test 2: "Table bar should exist"
# Looks like you failed 1 test of 2
Failed 1/2 subtests
Test Summary Report
-------------------
tests/tables.pg (Wstat: 0 Tests: 2 Failed: 1)
Failed test: 2
Files=1, Tests=2, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.03 cusr
0.00 csys = 0.05 CPU)
Result: FAIL
Cas d’usage
Cas d’usage
Zero Downtime Deployment
Déploiement successifs sur toutes les plateformes
qualif
staging EU
staging US
production FR
production US
production EU
Cas d’usage
$ l s −1
16.7−b−user−uuid−a−view . sql
16.7−b−user−uuid−b−drop−column . sql
16.7−c−account−view−manager . sql
16.7−d−account−view−employee . sql
16.7−e−document−extensions−drop−column . sql
. . .
tests /
Cas d’usage
r q u i e d e v i l l e db−cairo ~/16.7$ pg_prove −p 5491 −d rh2
tests /
tests /16.7−a . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok
tests /16.7−b . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok
tests /16.7−c . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok
tests /16.7−d . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok
tests /16.7− f−user−uuid−a . pg . . . . . . . . . . . . . . . . . . . . ok
tests /16.7− f−user−uuid−b . pg . . . . . . . . . . . . . . . . . . . . ok
tests /16.7− f−user−uuid−d . pg . . . . . . . . . . . . . . . . . . . . ok
tests /16.7− f−user−uuid−e . pg . . . . . . . . . . . . . . . . . . . . ok
tests /16.7− i −view−denorm . pg . . . . . . . . . . . . . . . . . . . . ok
tests /16.7− l −document−category−b . pg . . . . . . . . . . . . ok
tests /16.7− r−document−extensions−b−t r i g g e r . pg . . ok
tests /16.7−u−denorm . pg . . . . . . . . . . . . . . . . . . . . . . . . . ok
tests /16.7−v . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok
tests /16.7−w. pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok
A l l tests successful .
Files =14, Tests =58, 1 wallclock secs ( 0.06 usr 0.02
sys + 0.47
cusr 0.10 csys = 0.65 CPU)
Result : PASS
Pourquoi tout écrire
rodo@roz-desktop:~/$ pg_tapgen -d rodo
rodo@roz-desktop:~/$ cat schema.sql
SELECT views_are(’public’, ARRAY[
’pg_all_foreign_keys’,
’tap_funky’
]);
Indempotence
Index avant la 9.6
DO LANGUAGE plpgsql $$
BEGIN
IF _have_index (
’ public ’ ,
’ account_user ’ ,
’ account_user_date_delete_idx ’ )
THEN
DROP INDEX " account_user_date_delete_idx " ;
END IF ;
END;
$$ ;
Création d’utilisateur
DO LANGUAGE plpgsql $$
BEGIN
IF NOT _has_role ( ’ dba ’ ) THEN
CREATE ROLE dba ;
END IF ;
END;
$$ ;
Questions ?
Rodolphe Quiédeville
rodolphe.quiedeville@people-doc.com
Document publié sous Licence Creative Commons BY-SA 2.0

Tests unitaires pour PostgreSQL avec pgTap

  • 1.
    pgTap, tests unitairespour PostgreSQL Rodolphe Quiédeville Meetup PostgreSQL Nantes 22 juin 2016
  • 2.
    #mylife Découvert Internet à28.kbits avec Netscape Navigator Utilise et produit du logiciel libre exclusivement PostgreSQL depuis ... la 6.X ? Consultant en performance des SI(G) Data architect @PeopleDoc Formateur Upstream University
  • 3.
    Intro suite de fonctionspour faciliter l’écriture de tests au protocole TAP écrit en Perl et PL/pgSQL PostgreSQL 8.4 David E. Wheeler BSD like http://pgtap.org/
  • 4.
    Tap TAP, test anythingprotocol Initialement écrit pour Perl (1987), avec des implémentations aujourd’hui en C, C++, Python, PHP, Java, .... 1..4 ok 1 - Input file opened not ok 2 - First line of the input valid ok 3 - Read the rest of the file not ok 4 - Summarized correctly # TODO Not written yet
  • 5.
    Installation # apt-get installpgtap # apt-get install postgresql-9.1-pgtap ~# CREATE EXTENSION pgtap;
  • 6.
  • 7.
    Fonctions Test basique SELECT ok( 9 ^ 2 = 81 , ’ simple exponential ’ ) ;
  • 8.
    Fonctions Résultat de fonction SELECTi s ( ultimate_answer ( ) , 42 , ’ Meaning of L i f e ’ ) ;
  • 9.
    Fonctions Résultat de requête SELECTresults_eq ( ’SELECT ∗ FROM active_users ( ) ’ , ’SELECT ∗ FROM users WHERE active ’ , ’ active_users ( ) should return active users ’ ) ;
  • 10.
    Fonctions Test de schéma SELECThas_table ( ’myschema ’ : : name, ’ sometable ’ : : name) ;
  • 11.
    Fonctions Test de schéma SELECThas_tablespace ( ’ sometablespace ’ , ’ / data / dbs ’ ) ;
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
    Paramètres Paramètres des fonctions SELECThas_function (schema , function , args , description ) ; SELECT has_function (schema , function , args ) ; SELECT has_function (schema , function , description ) ; SELECT has_function (schema , function ) ; SELECT has_function ( function , args , description ) ; SELECT has_function ( function , args ) ; SELECT has_function ( function , description ) ; SELECT has_function ( function ) ;
  • 17.
    CAST everywhere List offunctions Schema | Name | Result data type | Argument data types | Type --------+--------------+------------------+--------------------------+-------- public | has_function | text | name | normal public | has_function | text | name, name | normal public | has_function | text | name, name[] | normal public | has_function | text | name, name, name[] | normal public | has_function | text | name, name, name[], text | normal public | has_function | text | name, name, text | normal public | has_function | text | name, name[], text | normal public | has_function | text | name, text | normal (8 rows)
  • 18.
    CAST everywhere ~# SELECThas_function ( ’ has_function ’ ) ; has_function --------------------------------------------- ok 8 - Function has_function() should exist (1 row)
  • 19.
    CAST everywhere ~# SELECThas_function ( ’ public ’ , ’ has_function ’ ) ; has_function --------------------------------- not ok 9 - has_function + # Failed test 9: ‘‘has_function’’ (1 row)
  • 20.
    CAST everywhere ~# SELECThas_function ( ’ public ’ : : name, ’ has_function ’ : : name) ; has_function ----------------------------------------------------- ok 10 - Function public.has_function() should exist (1 row)
  • 21.
    CAST everywhere PREPARE count_siteAS SELECT count ( ∗ ) FROM s i t e WHERE id < 0; SELECT results_eq ( ’ count_site ’ , ARRAY[ 0 ] , ’ check s i t e name ’ ) ;
  • 22.
    CAST everywhere results_eq ------------------------------------------------------------------- not ok2 - check site name + # Failed test 2: ‘‘check site name’’ + # Number of columns or their types differ between the queries (1 row)
  • 23.
    CAST everywhere ~# dfcount List of functions Schema | Name | Result data type | Argument data types | Type ------------+-------+------------------+---------------------+------ pg_catalog | count | bigint | | agg pg_catalog | count | bigint | "any" | agg (2 rows)
  • 24.
    CAST everywhere SELECT results_eq( ’ count_site ’ , ARRAY[ 0 : : b i g i n t ] , ’ check s i t e name ’ ) ;
  • 25.
    Organisation BEGIN; SELECT plan (7 ) ; SELECT has_table ( ’ domains ’ ) ; SELECT has_table ( ’ s t u f f ’ ) ; SELECT has_table ( ’ sources ’ ) ; SELECT has_table ( ’ domain_stuff ’ ) ; SELECT has_column ( ’ domains ’ , ’ id ’ ) ; SELECT col_is_pk ( ’ domains ’ , ’ id ’ ) ; SELECT has_column ( ’ domains ’ , ’ domain ’ ) ; SELECT ∗ FROM f i n i s h ( ) ; ROLLBACK;
  • 26.
    Organisation ~∗# SELECT plan( 7 ) ; plan −−−−−− 1..7 (1 row )
  • 27.
    Organisation ~∗# SELECT ∗FROM f i n i s h ( ) ; f i n i s h −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− # Looks l i k e you f a i l e d 7 tests of 7 (1 row )
  • 28.
    Erreur classique BEGIN; ~∗# SELECThas_table ( ’ s i t e ’ ) ; ERROR: P0001 : You t r i e d to run a t e s t without a plan ! Gotta have a plan CONTEXT: SQL statement ‘ ‘SELECT _get_latest ( ’ todo ’ ) ’ ’ PL /pgSQL function _todo ( ) l i n e 9 at assignment SQL statement ‘ ‘SELECT _todo ( ) ’ ’ PL /pgSQL function ok ( boolean , t e x t ) l i n e 9 at assignment LOCATION: exec_stmt_raise , pl_exec . c:3068 ROLLBACk;
  • 29.
    Pas de plan ~#SELECT no_plan ( ) ; no_plan −−−−−−−−− (0 rows ) ~# SELECT has_table ( ’ s i t e ’ ) ; has_table −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ok 1 − Table s i t e should e x i s t (1 row )
  • 30.
    Cas classique PREPARE create_siteAS INSERT INTO s i t e ( id , fqdn , sitename , uuid ) VALUES (−1, ’www. foo . bar ’ , ’ foobar ’ , uuid_generate_v4 ( ) ) , (−2, ’www. biz .com ’ , ’ Bizcom ’ , uuid_generate_v4 ( ) ) ; PREPARE check_site AS SELECT sitename FROM s i t e WHERE id = −1; SELECT lives_ok ( ’ create_site ’ , ’ [ SetUp ] create s i t e s ’ ) ; SELECT results_eq ( ’ check_site ’ , ARRAY[ ’ foobar ’ : : t e x t ] , ’ check s i t e name ’ ) ;
  • 31.
    Utilisation rodo@roz-desktop:~/meetNantes-3(master)$ pg_prove -drodo tests/ -v tests/tables.pg .. 1..2 ok 1 - Table items should exist not ok 2 - Table bar should exist # Failed test 2: "Table bar should exist" # Looks like you failed 1 test of 2 Failed 1/2 subtests Test Summary Report ------------------- tests/tables.pg (Wstat: 0 Tests: 2 Failed: 1) Failed test: 2 Files=1, Tests=2, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.03 cusr 0.00 csys = 0.05 CPU) Result: FAIL
  • 32.
  • 33.
    Cas d’usage Zero DowntimeDeployment Déploiement successifs sur toutes les plateformes qualif staging EU staging US production FR production US production EU
  • 34.
    Cas d’usage $ ls −1 16.7−b−user−uuid−a−view . sql 16.7−b−user−uuid−b−drop−column . sql 16.7−c−account−view−manager . sql 16.7−d−account−view−employee . sql 16.7−e−document−extensions−drop−column . sql . . . tests /
  • 35.
    Cas d’usage r qu i e d e v i l l e db−cairo ~/16.7$ pg_prove −p 5491 −d rh2 tests / tests /16.7−a . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok tests /16.7−b . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok tests /16.7−c . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok tests /16.7−d . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok tests /16.7− f−user−uuid−a . pg . . . . . . . . . . . . . . . . . . . . ok tests /16.7− f−user−uuid−b . pg . . . . . . . . . . . . . . . . . . . . ok tests /16.7− f−user−uuid−d . pg . . . . . . . . . . . . . . . . . . . . ok tests /16.7− f−user−uuid−e . pg . . . . . . . . . . . . . . . . . . . . ok tests /16.7− i −view−denorm . pg . . . . . . . . . . . . . . . . . . . . ok tests /16.7− l −document−category−b . pg . . . . . . . . . . . . ok tests /16.7− r−document−extensions−b−t r i g g e r . pg . . ok tests /16.7−u−denorm . pg . . . . . . . . . . . . . . . . . . . . . . . . . ok tests /16.7−v . pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok tests /16.7−w. pg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ok A l l tests successful . Files =14, Tests =58, 1 wallclock secs ( 0.06 usr 0.02 sys + 0.47 cusr 0.10 csys = 0.65 CPU) Result : PASS
  • 36.
    Pourquoi tout écrire rodo@roz-desktop:~/$pg_tapgen -d rodo rodo@roz-desktop:~/$ cat schema.sql SELECT views_are(’public’, ARRAY[ ’pg_all_foreign_keys’, ’tap_funky’ ]);
  • 37.
  • 38.
    Index avant la9.6 DO LANGUAGE plpgsql $$ BEGIN IF _have_index ( ’ public ’ , ’ account_user ’ , ’ account_user_date_delete_idx ’ ) THEN DROP INDEX " account_user_date_delete_idx " ; END IF ; END; $$ ;
  • 39.
    Création d’utilisateur DO LANGUAGEplpgsql $$ BEGIN IF NOT _has_role ( ’ dba ’ ) THEN CREATE ROLE dba ; END IF ; END; $$ ;
  • 40.