Teste die Datenbank ! Thomas Koch Senior Database Specialist think project! International GmbH & Co. KG Teste die Datenbank ! Warum? Womit? Wann? Wo? Was? Wie? Warum ist die Datenbank zu testen? ● Die Datenbank – ist das Fundament eines jeden Systems – ist eine Komponente in jeder Systemlandschaft – spiegelt das Geschäftsmodell wieder – enthält Geschäftslogik – … Vortrag: Teste die Datenbank ! 4/26 Thomas Koch think project! International GmbH & Co. KG Beispiel V-Modell Wann bzw. Wo finden die DB-Tests statt? Quelle: http://de.wikipedia.org/wiki/Datei:V-Modell.svg Vortrag: Teste die Datenbank ! 5/26 Thomas Koch think project! International GmbH & Co. KG Definition Teststufen Unit Test ● ● ● ● Auch Komponententest, Modultest Integrationstest ● Ein Test auf der Ebene der einzelnen Module der Software Testgegenstand ist die Funktionalität innerhalb einzelner abgrenzbarer Teile der Software – Module – Programme oder Unterprogramme – Units oder Klassen Testziel ist der Nachweis der technischen Lauffähigkeit und korrekter fachlicher (Teil-) Ergebnisse ● ● testet die Zusammenarbeit voneinander abhängiger Komponenten Testschwerpunkt liegt auf den Schnittstellen der beteiligten Komponenten Nachweis korrekte Ergebnisse über komplette Abläufe hinweg Quelle: http://de.wikipedia.org/wiki/Softwaretest Wo finden die Datenbank-Tests statt? Teststufen Quelle: http://www.agilecoachjournal.com/wp-content/uploads/2014/01/AgileTestingPyramid2.jpg Vortrag: Teste die Datenbank ! 7/26 Thomas Koch think project! International GmbH & Co. KG ● ● ● ● ● ● JUnit PHPUnit PerlUnit TAP NUnit JSUnit Komponte B Unit Test Komponte B Frontend Unit Test Unit Test ● ● ● ● ● ● ● ● ● ● ● ● JUnit PHPUnit PerlUnit TAP NUnit JSUnit JUnit Arquillian PHPUnit PerlUnit TAP NUnit Komponte B Komponte B Frontend Unit Test Unit Test Unit Test Komponte A Komponte B Integration Test Komponte B Integration Test Frontend ● ● ● ● ● ● ● ● ● ● ● ● JUnit PHPUnit PerlUnit TAP NUnit JSUnit JUnit Arquillian PHPUnit PerlUnit TAP NUnit Komponte B Komponte B Frontend Unit Test Unit Test Unit Test Komponte A Komponte B Integration Test Komponte B Frontend Integration Test ● ● Selenium Fit Komponte B Komponte B Akzeptanztest Frontend ● ● ● ● ● ● ● ● ● tSQLt pgTAP PGUnit myTAP DbFit PL/Unit DBUnit Test::DBUnit DBUnit.Net Datenbank Komponte B Frontend Unit Test Test Unit Unit Test Unit Test Datenbank Komponte B Integration Test Komponte B Frontend Integration Test ● ● Selenium Fit Datenbank Komponte B Akzeptanztest Frontend Was ist bei der Datenbank zu testen? Datenbankstruktur ● Existieren alle DBObjekte – – Geschäftslogik ● Stored Procedures ● Lasttests ● Trigger ● Laufzeiten von ● Constraints Tabellen Views – Stores Procedures – Unique Keys – DML-Operationen – Check – Stores Procedures – Constraints – Sequenzen ● Sequenzen – Operatoren ● Views – … ● Operatoren ● User/Rollen → Privilegien Views Foreign Keys Indexes Datenbankeigenschaften – – – ● Performance Vortrag: Teste die Datenbank ! 12/26 Thomas Koch think project! International GmbH & Co. KG Womit ist die PostgreSQL zu testen? Umgebung Tools Datenbankstruktur Geschäftslogik Performance ● check_postgres ● pgUnit ● JMeter ● pgTap ● pgTap ● pgTap ● liquibase ● SoapUI Auf allen ● ● ● ● LIVE, TEST, DEV, ... Nie auf LIVE Auf allen anderen ● ● ● TEST DEV ... ● ● ● ● Vortrag: LIVE ? TEST DEV ? ... Teste die Datenbank ! 13/26 Thomas Koch think project! International GmbH & Co. KG pgTap „pgTAP is a unit testing framework for PostgreSQL written in PL/pgSQL and PL/SQL. It includes a comprehensive collection of TAP-emitting assertion functions, as well as the ability to integrate with other TAP-emitting test frameworks. It can also be used in the xUnit testing style.“ (www.pgtap.org) ● Download http://pgxn.org/dist/pgtap/ ● Install database objects ● – CREATE EXTENSION pgtap; -- ab 9.1 – psql -d mydb -f pgtap.sql -- vor 9.1 873 functions 2 views Start with pg_prove (search.cpan.org) Vortrag: Teste die Datenbank ! 14/26 Thomas Koch think project! International GmbH & Co. KG Wie ist die Datenbankstruktur zu testen? Database Owner/Privileges PL/SQL Code ● has_tablespace ● db_owner_is ● has_function ● has_schema ● schema_owner_is ● has_cast ● has_type ● table_owner_is ● has_operator ● has_domain ● database_priv_are ● is_aggregate ● domain_type_is ● table_priv_are ● is_strict ● has_enum ● function_priv_are ● function_return ● has_language ● ● function_lang_is ● trigger_is ● ● is_superuser / isnt_superuser is_member_of has_user / has_role Quelle: http://pgtap.org/documentation.html#theschemathings Vortrag: Teste die Datenbank ! 15/26 Thomas Koch think project! International GmbH & Co. KG Wie ist die Datenbankstruktur zu testen? Table ● ● ● ● has_table has_column / columns_are col_is_null / col_not_null col_has_default / col_hasnt_default ● col_is_pk ● col_is_fk ● col_has_check Index / Constraint Other Objects ● has_fk ● has_sequence ● fk_ok ● has_view ● has_unique ● ● has_check ● ● has_index Quelle: http://pgtap.org/documentation.html#theschemathings Vortrag: has_materialized_ view has_trigger Teste die Datenbank ! 16/26 Thomas Koch think project! International GmbH & Co. KG Wie ist die Datenbankstruktur zu testen? SELECT has_schema('demo'); SELECT has_table('demo', 'tablexy', 'table public.tablexy exists'); SELECT has_sequence('demo', 'seq_tablexy_id', 'Sequence demo.seq_tablexy_id exists'); SELECT has_column('demo', 'tablexy', 'last_change_dt', 'Column demo.tablexy.last_change_dt exists'); SELECT col_type_is('demo', 'tablexy', 'id', 'bigint', 'Type of column demo.tablexy.id is bigint'); SELECT col_is_fk('demo', 'tablexy', ARRAY[ 'fk_id' ], 'FOREIGN KEY on column tablexy.fk_id exists'); SELECT col_is_pk('demo', 'tablexy', ARRAY[ 'id' ], 'PRIMARY KEY on columnn tablexy.id exists'); SELECT col_default_is('demo', 'tablexy', 'id','nextval(''demo.seq_tablexy_id''::regclass)', 'Column demo.tablexy.id has a default value: nextval(''demo.seq_tablexy_id''::regclass)'); SELECT has_trigger('demo', 'tablexy', 'triu_tablexy', 'INSERT/UPDATE Trigger demo.triu_tablexy on table tablexy should exists'); SELECT trigger_is('demo', 'tablexy', 'triu_tablexy', 'demo', 'tr_fnc', 'Trigger demo.triu_tablexy use function demo.tr_fnc()'); SELECT index_is_type('demo', 'tablexy', 'ix_tablexy_cols', 'btree', 'Index type is btree(demo.tablexy.ix_tablexy_cols)'); SELECT has_index('demo', 'tablexy', 'ix_tablexy_cols', ARRAY[ 'col1','col2'], 'Index demo.ix_tablexy_cols should exists'); Vortrag: Teste die Datenbank ! 17/26 Thomas Koch think project! International GmbH & Co. KG Wie ist die Datenbankstruktur zu testen? SELECT ok('{}'::json = '{}'::json, 'check operator =(json,json)'); SELECT has_view('demo', 'v_xyz', 'View demo.v_xyz should exists'); SELECT has_type('demo', 'enumxy_type', 'Type demo.enumxy_type exists'); SELECT has_function('demo', 'fnc_xy', 'Function demo.fnc_xy should exists'); SELECT schema_privs_are ( 'demo', 'demouser', ARRAY['USAGE'], 'demouser should be granted USAGE on schema demo'); SELECT roles_are(ARRAY[ 'postgres', 'demouser', 'deploy', 'icinga']); pg_prove -h dbhost -d dbname -U dbuser test*.sql Files=11 Tests=5742 Time=14sec Vortrag: Teste die Datenbank ! 18/26 Thomas Koch think project! International GmbH & Co. KG Generierung der DB-Strukturtests ● ● komplett – Liquibase – check_postgres – pg_tapgen Individuell anpassen – Struktur aus DB-Systemobjekte holen – Mit Filter einschränken SELECT 'SELECT has_view(''' || table_schema || ''', ''' || table_name || ''', ''View ' || table_schema || '.' || table_name || ' should exists'');' AS pgtap FROM INFORMATION_SCHEMA.views WHERE table_schema NOT IN ('pg_catalog','information_schema') AND table_name NOT LIKE 'dba%' ORDER BY table_schema DESC Vortrag: Teste die Datenbank ! 19/26 Thomas Koch think project! International GmbH & Co. KG DB-Strukturtest – Übung CREATE VIEW v_mitarbeiter AS SELECT name || ', ' || vorname AS name FROM mitarbeiter; Beispiel CREATE OR REPLACE FUNCTION is_lastday(i_date TIMESTAMP) RETURNS BOOLEAN AS $$ SELECT CASE WHEN DATE(((date_trunc('month', i_date)+interval '1 month')­interval '1 day')) = DATE(i_date) THEN TRUE ELSE FALSE END $$ LANGUAGE SQL IMMUTABLE STRICT; Test BEGIN; SET search_path TO pgtap, public; SELECT * FROM plan(11); ­­ test SELECT has_view('public', 'v_mitarbeiter', 'View public.v_mitarbeiter should exists'); SELECT view_owner_is( 'public', 'v_mitarbeiter', 'tkoch', 'owner of view public.v_mitarbeiter is tkoch'); SELECT lives_ok('SELECT * FROM v_mitarbeiter LIMIT 0', 'view v_mitarbeiter should run'); SELECT has_column('public', 'v_mitarbeiter', 'name', 'Column public.v_mitarbeiter.name exists'); SELECT has_function('public', 'is_lastday', 'Function public.is_lastday should exists'); SELECT is_strict('public', 'is_lastday', 'Function public.is_lastday should strict'); SELECT volatility_is( 'public', 'is_lastday', ARRAY['timestamp without time zone'], 'immutable' ); SELECT function_owner_is('public', 'is_lastday', ARRAY['timestamp without time zone'],'tkoch', 'owner of function public.is_lastday is tkoch'); SELECT function_returns('public', 'is_lastday', ARRAY['timestamp without time zone'], 'boolean', 'return type of function public.is_lastday is boolean'); SELECT function_lang_is('public', 'is_lastday', ARRAY['timestamp without time zone'], 'sql', 'language of function public.is_lastday is sql'); SELECT function_privs_are('public', 'is_lastday', ARRAY['timestamp without time zone'],'tkoch', ARRAY['EXECUTE'], 'function public.is_lastday has privileges: EXECUTE'); SELECT * FROM finish(); ROLLBACK; Vortrag: Teste die Datenbank ! 20/26 Thomas Koch think project! International GmbH & Co. KG Wie ist die Geschäftslogik zu testen? ● SELECT ok(:boolean, :description); ● SELECT is(:have, :want, :description); ● SELECT matches(:have, :regex, :description); ● SELECT throws_ok(:sql, :errcode, :ermsg, :description); ● SELECT throws_like(:sql, :like, :description); ● SELECT throws_matching(:sql, :regex, :description); ● SELECT results_eq(:sql, :sql, :description); ● SELECT set_eq(:sql, :sql, :description); ● SELECT set_has(:sql, :sql, :description); ● SELECT is_empty(:sql, :description); ● SELECT row_eq(:sql, :record, :description); Quelle: http://pgtap.org/documentation.html Vortrag: Teste die Datenbank ! 21/26 Thomas Koch think project! International GmbH & Co. KG Wie ist die Geschäftslogik zu testen? CREATE VIEW v_mitarbeiter AS SELECT name || ', ' || vorname AS name FROM mitarbeiter; Beispiel CREATE OR REPLACE FUNCTION is_lastday(i_date TIMESTAMP) RETURNS BOOLEAN AS $$ SELECT CASE WHEN DATE(((date_trunc('month', i_date)+interval '1 month')­interval '1 day')) = DATE(i_date) THEN TRUE ELSE FALSE END $$ LANGUAGE SQL IMMUTABLE STRICT; Test BEGIN; SET search_path TO pgtap, public; SELECT * FROM plan(6); – prepare TRUNCATE TABLE mitarbeiter; INSERT INTO mitarbeiter (name, vorname) VALUES ('Zufall','Rainer'); – test SELECT isnt_empty($$SELECT name FROM v_mitarbeiter $$, 'test v_mitarbeiter not empty'); SELECT results_eq($$SELECT name FROM v_mitarbeiter $$, $$SELECT 'Zufall, Rainer'::TEXT $$,'test v_mitarbeiter concat'); SELECT is( is_lastday('2012­02­29'), TRUE, 'test is_lastday from 2012­02­29' ); SELECT is( is_lastday('2014­02­28'), TRUE, 'test is_lastday from 2014­02­28' ); SELECT is( is_lastday('2012­02­28'), FALSE, 'test is_lastday from 2012­02­28' ); SELECT is( is_lastday('2012­06­30'), TRUE, 'test is_lastday from 2012­06­30' ); Files=20 Tests=108 Time=4sec SELECT * FROM finish(); ROLLBACK; Vortrag: Teste die Datenbank ! 22/26 Thomas Koch think project! International GmbH & Co. KG Wie ist die Performance zu testen? SELECT performs_ok(:sql, :milliseconds, :description); SELECT performs_within(:sql, :average_milliseconds, :within, :iterations, :description); BEGIN; SET search_path TO pgtap, public; SELECT * FROM plan(2); – prepare TRUNCATE TABLE mitarbeiter; COPY mitarbeiter FROM '/tmp/mitarbeiter_data' (DELIMITER ','); – test PREPARE fast_query AS SELECT name FROM v_mitarbeiter WHERE name LIKE 'Zufall'; SELECT performs_ok('fast_query', 250, 'select by name should be fast'); SELECT performs_within('fast_query', 250, 10, 100, 'select by name should be fast'); SELECT * FROM finish(); ROLLBACK; Quelle: http://pgtap.org/documentation.html Vortrag: Teste die Datenbank ! 23/26 Thomas Koch think project! International GmbH & Co. KG Automatisierung ● pg_prove in Build-Tools integrieren – Jenkins (TAP Plugin bzw. xUnit Plugin) – Buildbot ● pg_prove in Deployment-Skripte integrieren ● pg_prove in crontab aufnehmen Vortrag: Teste die Datenbank ! 24/26 Thomas Koch think project! International GmbH & Co. KG Zusammenfassung ● ● ● ● Warum? – Fundament eines Systems – nur eine weitere Komponente – Geschäftmodell und Geschäftslogik Wann und Wo? – Eher direkt auf der Datenbank – mehr Unit Tests als Integrationstests Was? – Struktur der Datenbank – Geschäftslogik in der Datenbank – Performance der Datenbank Womit? – ● für jede DB gibt es mehrere Alternativen (hier pgTAP gezeigt) Wie? – am Besten in einer Transaktion mit Rollback am Ende – im Vorbereitungsschritt die Daten löschen und mit eigenen Daten befüllen – Tests der DB-Struktur können generiert werden Vortrag: Teste die Datenbank ! 25/26 Thomas Koch think project! International GmbH & Co. KG Teste die Datenbank ! Thomas Koch Senior Database Specialist think project! International GmbH & Co. KG Mail: [email protected]