600 likes | 722 Views
Using pgTap to unit test your functions and queries. By Lloyd Albin. pgTAP. pgTAP : Unit Testing for PostgreSQL.
E N D
Using pgTap to unit test your functions and queries By Lloyd Albin pgTAP
pgTAP pgTAP
pgTAP: Unit Testing for PostgreSQL • pgTAP is a suite of database functions that make it easy to write TAP-emitting unit tests in psql scripts or xUnit-style test functions. The TAP output is suitable for harvesting, analysis, and reporting by a TAP harness, such as those used in Perl applications. • http://pgtap.org/ • Good for: • Application Development • Schema Validation • xUnit-Style Testing • Module Development pgTAP
pgTAP pgTAP
Installing pg_tap • Installation will require you to download the pgTAP source code and then compile and install pgTAP against your installed version of Postgres. • It is also a good idea to install pg_prove. pgTAP
Test Directory • In our test directory we want to create some folders specific to the tests we are going to run. • Create a common directory. • Create a find_raster directory (Name of the function we are going to write.) mkdir commonmkdirfind_raster pgTAP
Common Diagnostics pgTAP
Common Diagnostics • I like to create a common diagnostics that I run against all databases. • common/diagnostic.sql -- Turn off echo and keep things quiet. \set ECHO \set QUIET 1-- Format the output for nice TAP. \pset format unaligned \psettuples_only true \pset pager -- Revert all changes on failure. \set ON_ERROR_ROLLBACK 1\set ON_ERROR_STOP true \set QUIET 1-- Begin the transaction. BEGIN; SETsearch_pathTOpublic; pgTAP
Set user to test as • If you are not running as the owner of the database, switch to be the owner of the database. This is use full if you are testing as user postgres, otherwise this might be skipped. -- Inline function to set the role for extension installation DO $BODY$ DECLAREdb_owner record; BEGINSELECTpg_user.usenameINTOdb_ownerFROMpg_databaseLEFTJOINpg_catalog.pg_userONpg_database.datdba=pg_user.usesysidWHEREdatname=current_database(); IFdb_owner.usename<>current_userTHENEXECUTE'SET ROLE '||db_owner.usename; SETsearch_pathTOpublic; ENDIF; END$BODY$ LANGUAGE plpgsql; pgTAP
Install pgtap extension • We want to install the pgtap extension if it is not already installed. Because this is inside a transaction if the extension gets installed it will be automatically uninstalled at the end of the tests. • Write certain psql variables to a temp table to test against. -- Install the TAP functions if it is not already installed. CREATE EXTENSION IFNOTEXISTSpgtap; -- Set the role to the user you wish to run the tests as. CREATETEMPTABLE __pgtap_db_server__ (server text, username text, production_databasetext); INSERTINTO __pgtap_db_server__ (server, username, production_database) VALUES (:'HOST', :'test_user', :'test_production_database'); pgTAP
pg_prove • /usr/local/apps/perl/perl-current/bin/pg_prove-h sqltest-alt -d sandbox --recurse -v --extsqlfind_raster DO $BODY$ DECLAREserver_name record; BEGINSELECT server, username, production_databaseINTOserver_nameFROM __pgtap_db_server__; IFserver_name.production_database=current_database() THEN-- If production database, run as the owner of the database PERFORM 'SET ROLE '||server_name.username; SETsearch_pathTOpublic; --SELECT diag('Running on a production database'); ELSE-- If not a production database, run as the executing user aka developer RESET ROLE; SETsearch_pathTOpublic; ENDIF; END$BODY$ LANGUAGE plpgsql; pgTAP
Validation Information • We need to say how many tests we are going to perform. • The diag command lets us generate output that is not a test so that we can show current variables, database, database version, timestamp, etc that is required for validation. -- Plan the tests. SELECTplan(:plan+16); -- Configuration Data SELECTdiag('Configuration'); SELECTdiag('================================='); SELECTdiag('Test Name: '|| :'test_name'); SELECTdiag('Date: '||current_timestamp); SELECTdiag('Current Server: '|| :'HOST'); SELECTdiag('Current Database: '||current_database()); SELECTdiag('Current Port: '|| :'PORT'); SELECTdiag(''); SELECTdiag('Current Session User: '||session_user); SELECTdiag('Current User: '||current_user); SELECTdiag('pgTAP Version: '||pgtap_version()); SELECTdiag('pgTAP Postgres Version: '||pg_version()); SELECTdiag('Postgres Version: '||current_setting( 'server_version')); SELECTdiag('OS: '||os_name()); SELECTdiag(''); SELECTdiag('Common Tests'); SELECTdiag('================================='); pgTAP
Checking extensions • Ok tests true/false • SELECT ok( :boolean, :description ); • Is test equals • SELECT is( :have, :want, :description ); SELECT ok((SELECTCASEWHENcurrent_setting( 'server_version_num') =pg_version_num()::textTHEN TRUE ELSE FALSE END), 'pgTAP is compiled against the correct Postgres Version'); SELECTis( (SELECTextnameFROMpg_catalog.pg_extensionWHEREextname='plpgsql') , 'plpgsql', 'Verifying extension plpgsql is installed'); SELECTis( (SELECTextnameFROMpg_catalog.pg_extensionWHEREextname='plperl') , 'plperl', 'Verifying extension plperl is installed'); SELECTis( (SELECTextnameFROMpg_catalog.pg_extensionWHEREextname='pgtap') , 'pgtap', 'Verifying extension pgtap is installed'); pgTAP
Testing Optional Extensions • If we are in the postgres database we want to check for two more extensions otherwise we want to skip testing for those extensions. • collect_tap performs multiple tests at once. • SELECT collect_tap(:lines); • Skip allows you to skip tests that you have allotted with plan(x). • SELECT skip( :why, :how_many ); SELECTCASEWHENcurrent_database() ='postgres'THENcollect_tap( is( (SELECTextnameFROMpg_catalog.pg_extensionWHEREextname='adminpack') , 'adminpack', 'Verifying extension adminpack is installed'), is( (SELECTextnameFROMpg_catalog.pg_extensionWHEREextname='pg_buffercache') , 'pg_buffercache', 'Verifying extension pgbuffercache is installed'), is( (SELECTcount(*)::intFROMpg_catalog.pg_extension) , 5, 'Verifying only 5 extensions are installed') ) ELSEcollect_tap( skip('Skipping extenstion test for adminpack', 1), skip('Skipping extenstion test for pg_buffercache', 1), is( (SELECTcount(*)::intFROMpg_catalog.pg_extension) , 3, 'Verifying only 3 extensions are installed') ) END; pgTAP
Testing for Extra Extensions • This will list all the extra extensions installed. SELECTdiag('Extra Extension Installed: '||extname) FROMpg_catalog.pg_extensionWHERE ( current_database() ='postgres'ANDextname!='plpgsql'ANDextname!='plperl'ANDextname!='pgtap'ANDextname!='adminpack'ANDextname!='pg_buffercache') OR ( current_database() <>'postgres'ANDextname!='plpgsql'ANDextname!='plperl'ANDextname!='pgtap'); pgTAP
Testing Languages installed • We are going to test that we have the correct languages installed and make sure that we do not have the un-secure perl installed. • has_language checks for the installed langauge. • SELECT has_language( :language ); • hasnt_language check to make sure it is not installed. • SELECT hasnt_language( :language ); SELECThas_language( 'c' ); SELECThas_language( 'internal' ); SELECThas_language( 'sql' ); SELECThas_language( 'plpgsql' ); SELECThas_language( 'plperl' ); SELECThasnt_language( 'plperlu' ); pgTAP
Testing for Extra Languages • This test will fail if we have extra languages installed and then list the extra languages. SELECTis( (SELECTcount(*)::intFROMpg_catalog.pg_language) , 5, 'Verifying no extra languages are installed'); SELECTdiag('Extra Languages Installed: '||lanname) FROMpg_catalog.pg_languageWHERElanname!='c'ANDlanname!='internal'ANDlanname!='sql'ANDlanname!='plpgsql'ANDlanname!='plperl'; pgTAP
Testing for perl bug. • We once ran into a bug with perl when upgrading. This tests that multiplicity is defined within the perl otherwise perl will not work properly with Postgres. • lives_ok tests a prepared statement or sql and makes sure that it does not error out. • SELECT lives_ok( :sql, :description ); SET ROLE postgres; SETsearch_pathTOpublic; CREATEFUNCTIONpublic.perl_test () RETURNSintegerAS$body$ return1; $body$ LANGUAGE 'plperl'VOLATILE CALLED ONNULL INPUT SECURITY INVOKER; GRANTEXECUTEONFUNCTIONpublic.perl_test() TOPUBLIC; PREPAREdba_perl_testASSELECT*FROMpublic.perl_test(); SELECTlives_ok('dba_perl_test','Testingplperl has multiplicity defined - Test 1'); SET ROLE dba; SETsearch_pathTOpublic; SELECTlives_ok('dba_perl_test','Testingplperl has multiplicity defined - Test 2'); RESET ROLE; pgTAP
Change back to our test user and start the tests • Here we set the role back to our testing user and get ready to start testing. DO $BODY$ DECLAREserver_name record; BEGINSELECT server, username, production_databaseINTOserver_nameFROM __pgtap_db_server__; IFserver_name.production_database=current_database() THEN-- If production database, run as the owner of the database PERFORM 'SET ROLE '||server_name.username; --SELECT diag('Running on a production database'); ELSE-- If not a production database, run as the executing user aka developer RESET ROLE; ENDIF; END$BODY$ LANGUAGE plpgsql; SELECTdiag('Tests'); SELECTdiag('================================='); pgTAP
Running the basic tests pgTAP
pg_prove • Inside our find_raster directory we want to create a 01_find_raster.sql file. • This file right now is only going to test our common diagnostics file. -- Setup Test Variables \settest_name'find_raster'\settest_user'lalbin'\settest_production_database'sandbox'\setplan0-- Install pgTAP, show diagnostics, and start common tests \ir ../common/diagnostic.sql pgTAP
pg_prove • pg_prove • -h host • -d database • --recurse Follow the directories recursively • -v Verbose mode, shows you each test • --extsql The extension of the files you will be using. • find_raster The name of the directory to start with. /usr/local/apps/perl/perl-current/bin/pg_prove-h sqltest-alt -d sandbox --recurse-v --extsqlfind_raster pgTAP
Running our test • Here is the general information for validation. 1..16# Configuration# =================================# Test Name: find_raster# Date: 2017-01-03 15:26:37.274103-08# Current Server: sqltest-alt# Current Database: sandbox# Current Port: 5432## Current Session User: postgres# Current User: postgres# pgTAP Version: 0.97# pgTAP Postgres Version: 9.4.9# Postgres Version: 9.4.9# OS: linux# pgTAP
Running our test • Here are the results of our general testing. # Common Tests# =================================ok 1 - pgTAP is compiled against the correct Postgres Versionok 2 - Verifying extension plpgsql is installedok 3 - Verifying extension plperl is installedok 4 - Verifying extension pgtap is installedok 5 # SKIP Skipping extenstion test for adminpackok 6 # SKIP Skipping extenstion test for pg_buffercacheok 7 - Verifying only 3 extensions are installedok 8 - Procedural language c should existok 9 - Procedural language internal should existok 10 - Procedural language sql should existok 11 - Procedural language plpgsql should existok 12 - Procedural language plperl should existok 13 - Procedural language plperlu should not existok 14 - Verifying no extra languages are installedok 15 - Testing plperl has multiplicity defined - Test 1ok 16 - Testing plperl has multiplicity defined - Test 2 pgTAP
Running our test • Now we started our find_raster testing, for which we have not yet written any tests. • So everything passed ok so far. • Now we are ready to starting writing out tests and function. # Tests# =================================okAll tests successful.Files=1, Tests=16, 0 wallclock secs ( 0.02 usr 0.02 sys + 0.00 cusr 0.02 csys = 0.06 CPU)Result: PASS pgTAP
find_raster pgTAP
The Problem We receive faxes that are multi-page TIFF’s. The TIFF file name are called the raster id. We have data where we have the full path of the file name, the raster id and the raster id with page number. Examples: • 0000/000000 • 0000/0000001111 • /studydata/studyname/0000/000000 pgTAP
Writing our tests • We now change the plan to 3 tests. • We write our basic three tests. • Now we are ready to write our function. \setplan3-- Install pgTAP, show diagnostics, and start common tests \ir ../common/diagnostic.sqlSELECTis((SELECTfind_raster('1234/567890')), '1234/567890', 'Testing find_raster using Raster ID'); SELECTis((SELECTfind_raster('1234/5678901234')), '1234/567890', 'Testing find_raster using Raster ID with Page Number'); SELECTis((SELECTfind_raster('/study_data/study_name/1234/567890')), '1234/567890', 'Testing find_raster using Directory path with File Name'); pgTAP
find_raster function The first thing to do, is to be able to find the Raster ID, no matter which format is supplied. IMMUTABLE is required to be able to use this function as part of an index. This function was written to fit on this page. Some of the other items to put inside the real function would be: • We should check the return to make sure a ‘/’ is in the correct spot and through an error if it is not. “R” is also valid. • If the raster is less then 23 characters to return an error, except for when it is 11 or 15 in length. CREATEFUNCTIONfind_raster (raster varchar) RETURNSVARCHAR(11) AS$$ BEGINCASE length(raster) WHEN11THEN-- Format: 1234/567890-- Returns: 1234/567890 RETURN raster; WHEN15THEN-- Format: 1234/5678901234-- Returns: 1234/567890 RETURNsubstr(raster, 1, 11); ELSE-- Format: /study_data/study_name/1234/567890-- Returns: 1234/567890 RETURNsubstr(raster, length(raster)-10, 11); ENDCASE; END; $$ LANGUAGE plpgsqlIMMUTABLE RETURNSNULLONNULL INPUT; pgTAP
Testing our new function The first thing to do, is to be able to find the Raster ID, no matter which format is supplied. IMMUTABLE is required to be able to use this function as part of an index. This function was written to fit on this page. Some of the other items to put inside the real function would be: • We should check the return to make sure a ‘/’ is in the correct spot and through an error if it is not. “R” is also valid. • If the raster is less then 23 characters to return an error, except for when it is 11 or 15 in length. # Tests # ================================= ok 17 - Testing find_raster using Raster ID ok 18 - Testing find_raster using Raster ID with Page Number ok 19 - Testing find_raster using Directory path with File Name ok All tests successful. Files=1, Tests=19, 0 wallclock secs ( 0.05 usr 0.01 sys + 0.00 cusr 0.02 csys = 0.08 CPU) Result: PASS pgTAP
Now we run across bad data Lets say the ‘/’ ended up being an ‘X’ so we need to make the function fail and test for that failure. But ‘R’ is also a valid character to we need to make sure the function does not break with an ‘R’. So we now need to update our tests and code. • Update existing test comments to say ‘/’ and add test for ‘R’ and ‘X’. • throws_ok allows us to test for a specific error message. • SELECT throws_ok( :sql, :errmsg, :description ); \set plan 9 …SELECTis((SELECTfind_raster('/study_data/study_name/1234R567890')), '1234R567890', 'Testing R find_raster using Directory path with File Name'); SELECTthrows_ok(E'SELECTfind_raster(''1234X567890'')', 'Invalid Character "X"', 'Testing X find_raster using Raster ID'); SELECTthrows_ok(E'SELECTfind_raster(''1234X5678901234'')', 'Invalid Character "X"', 'Testing X find_raster using Raster ID with Page Number'); SELECTthrows_ok(E'SELECTfind_raster(''/study_data/study_name/1234X567890'')', 'Invalid Character "X"', 'Testing X find_raster using Directory path with File Name'); pgTAP
Now if we test before updating the code If we run the tests before we update the code, we will see how the find_raster function does not fail for the X condition. So now we need to fix the code and test again. ok 17 - Testing / find_raster using Raster ID ok 18 - Testing / find_raster using Raster ID with Page Number ok 19 - Testing / find_raster using Directory path with File Name ok 20 - Testing R find_raster using Raster ID ok 21 - Testing R find_raster using Raster ID with Page Number ok 22 - Testing R find_raster using Directory path with File Name not ok 23 - Testing X find_raster using Raster ID # Failed test 23: "Testing X find_raster using Raster ID" # caught: no exception # wanted: an exception not ok 24 - Testing X find_raster using Raster ID with Page Number # Failed test 24: "Testing X find_raster using Raster ID with Page Number" # caught: no exception # wanted: an exception not ok 25 - Testing X find_raster using Directory path with File Name # Failed test 25: "Testing X find_raster using Directory path with File Name" # caught: no exception # wanted: an exception Failed 3/25 subtests (less 2 skipped subtests: 20 okay) Test Summary Report ------------------- find_raster/01_find_raster.sql (Wstat: 0 Tests: 25 Failed: 3) Failed tests: 23-25 Files=1, Tests=25, 1 wallclock secs ( 0.06 usr 0.01 sys + 0.00 cusr 0.02 csys = 0.09 CPU) Result: FAIL pgTAP
Update the function We now test for ‘/’ and ‘R’ and for any other character we raise an exception. Ready to test again. DECLAREraster_idVARCHAR; BEGINCASElength(raster) WHEN11THENraster_id:= raster; WHEN15THENraster_id:=substr(raster, 1, 11); ELSEraster_id:=substr(raster, length(raster)-10, 11); ENDCASE; CASEsubstr(raster_id, 5, 1) WHEN'/'THENWHEN'R'THENELSE RAISE EXCEPTION 'Invalid Character "%"', substr(raster_id, 5, 1); ENDCASE; RETURNraster_id; END; pgTAP
Testing with the fixed function. Now it passes all the test. Finding the good raster id’s and failing for the bad raster id’s. ok 17 - Testing / find_raster using Raster ID ok 18 - Testing / find_raster using Raster ID with Page Number ok 19 - Testing / find_raster using Directory path with File Name ok 20 - Testing R find_raster using Raster ID ok 21 - Testing R find_raster using Raster ID with Page Number ok 22 - Testing R find_raster using Directory path with File Name ok 23 - Testing X find_raster using Raster ID ok 24 - Testing X find_raster using Raster ID with Page Number ok 25 - Testing X find_raster using Directory path with File Name ok All tests successful. Files=1, Tests=25, 0 wallclock secs ( 0.03 usr 0.01 sys + 0.00 cusr 0.01 csys = 0.05 CPU) Result: PASS pgTAP
Now lets test for bad lengths If the raster is less then 23 characters to return an error, except for when it is 11 or 15 in length. SELECTthrows_ok(E'SELECT find_raster(''1234X5678'')', 'Invalid Raster Length: 9', 'Testing find_raster using bad Raster ID'); SELECTthrows_ok(E'SELECT find_raster(''1234X56789012'')', 'Invalid Raster Length: 13', 'Testing find_raster using bad Raster ID with Page Number'); SELECTthrows_ok(E'SELECT find_raster(''/a/z/1234X567890'')', 'Invalid Raster Length: 16', 'Testing find_raster using bad Directory path with File Name'); pgTAP
Update the function, again If the raster is less then 23 characters to return an error, except for when it is 11 or 15 in length. BEGINCASEWHEN length(raster) =11THENWHEN length(raster) =15THENWHEN length(raster) <23THEN RAISE EXCEPTION 'Invalid Raster Length: %', length(raster); ELSEENDCASE; pgTAP
Testing with the fixed function. Now it passes all the test. Finding the good raster id’s and failing for the bad raster id’s. ok 17 - Testing / find_raster using Raster ID ok 18 - Testing / find_raster using Raster ID with Page Number ok 19 - Testing / find_raster using Directory path with File Name ok 20 - Testing R find_raster using Raster ID ok 21 - Testing R find_raster using Raster ID with Page Number ok 22 - Testing R find_raster using Directory path with File Name ok 23 - Testing X find_raster using Raster ID ok 24 - Testing X find_raster using Raster ID with Page Number ok 25 - Testing X find_raster using Directory path with File Name ok 26 - Testing find_raster using bad Raster ID ok 27 - Testing find_raster using bad Raster ID with Page Number ok 28 - Testing find_raster using bad Directory path with File Name ok All tests successful. Files=1, Tests=28, 0 wallclock secs ( 0.05 usr 0.03 sys + 0.01 cusr 0.02 csys = 0.11 CPU) Result: PASS pgTAP
Lets setup some tables • Here we are setting up some tables to test with. CREATETABLEpublic.table_a ( id SERIAL, name VARCHAR, PRIMARYKEY(id) ) WITH (oids= false); CREATETABLEpublic.table_b ( id SERIAL, a_idINTEGER, status VARCHAR, action_dtTIMESTAMP WITHOUT TIME ZONE, PRIMARYKEY(id) ) WITH (oids= false); pgTAP
Lets setup a poor view • We are wanting the employee’s current status for all currently employed employees. CREATEORREPLACEVIEWpublic.view_aASSELECT a.id, a.name, b.status, b.action_dtFROMtable_a a LEFTJOIN ( SELECT table_b.id, table_b.a_id, table_b.status, table_b.action_dtFROMtable_bLEFTJOIN ( SELECTmax(table_b_1.id) AS id, table_b_1.a_id FROMtable_b table_b_1 GROUPBY table_b_1.a_id) c USING (id, a_id) WHERE c.id ISNOTNULL) b ON a.id =b.a_idWHEREb.status!='Left' ORDER BY a.id; pgTAP
Truncate the tables (17) • Make a directory called employee_test and create a file called 01_employee_test.sql • First we want to truncate all our tables. While this could have been just a normal SQL command, we can wrap it inside a lives_ok to verify that it worked without any errors. -- Setup Test Variables \settest_name'employee_status'\settest_user'lalbin'\settest_production_database'sandbox'\setplan4-- Install pgTAP, show diagnostics, and start common tests \ir ../common/diagnostic.sql--\i common/diagnostic.sqlSELECTlives_ok('TRUNCATE table_a, table_b', 'Truncating tables in preperation for testing'); pgTAP
Insert the test data (18 & 19) • Next we need to insert our test records. SELECTlives_ok(E'INSERT INTO table_a VALUES (1, ''Lloyd''), (2, ''Judy''), (3, ''Gerald'')', 'Inserting Test Data into table_a'); SELECTlives_ok(E'INSERT INTO table_b VALUES (1, 1, ''Hired'', ''1/1/14''), (2, 2, ''Hired'', ''1/1/15''), (3, 3, ''Hired'', ''1/1/16''), (4, 2, ''Maternity'', ''4/5/16''), (5, 2, ''Normal'', ''5/5/16''), (6, 3, ''Left'', ''10/15/16'')', 'Inserting Test Data into table_b'); pgTAP
Testing the data (20) • Use results_eq to test the output of the view. By default the values will have a field type of “unknown” and that is why we need to cast the variables in the first row to match the query output. • SELECT results_eq( :sql, :sql, :description ); SELECTresults_eq( 'SELECT id, name, status, action_dt FROM view_a;', $$VALUES (1,'Lloyd'::varchar,'Hired'::varchar,'2014-01-01'::timestamp), (2,'Judy','Normal','2016-05-05')$$, 'Verify Data in view_a'); pgTAP
Run the Initial Test • We get the results we expect. # Tests # ================================= ok 17 - Truncating tables in preperation for testing ok 18 - Inserting Test Data into table_aok 19 - Inserting Test Data into table_bok 20 - Verify Data in view_aok All tests successful. Files=1, Tests=20, 0 wallclock secs ( 0.06 usr 0.01 sys + 0.00 cusr 0.04 csys = 0.11 CPU) Result: PASS pgTAP
Creating a failure test (21 & 22) • Now we need to write a new test to cause the bug that we wish to test. This is caused by entering the data out of order. SELECTlives_ok(E'INSERT INTO table_b VALUES (7, 2, ''Normal'', ''10/1/16''), (8, 2, ''Family Leave'', ''9/1/16'')', 'Inserting 2nd Test Data into table_b'); SELECTresults_eq( 'SELECT id, name, status, action_dt FROM view_a;', $$VALUES (1,'Lloyd'::varchar,'Hired'::varchar,'2014-01-01'::timestamp), (2,'Judy','Normal','2016-10-01')$$, 'Verify Data in view_a'); pgTAP
Run the Failure Test • Here we see where the view failed and the bad line vers the good line. # Tests # ================================= ok 17 - Truncating tables in preperation for testing ok 18 - Inserting Test Data into table_aok 19 - Inserting Test Data into table_bok 20 - Verify Data in view_aok 21 - Inserting 2nd Test Data into table_bnot ok 22 - Verify Data in view_a# Failed test 22: "Verify Data in view_a" # Results differ beginning at row 2: # have: (2,Judy,"Family Leave","2016-09-01 00:00:00") # want: (2,Judy,Normal,"2016-10-01 00:00:00") Failed 1/22 subtests (less 2 skipped subtests: 19 okay) Test Summary Report ------------------- employee_test/01_employee_test.sql (Wstat: 0 Tests: 22 Failed: 1) Failed test: 22 Files=1, Tests=22, 1 wallclock secs ( 0.06 usr 0.01 sys + 0.01 cusr 0.02 csys = 0.10 CPU) Result: FAIL pgTAP
Refactoring the Query • Re-factoring the query to have the correct status and at the same time switching to a higher performing query, as long as you create the index needed by the ORDER BY statement. • After some use, you notice that this query starts slowing down and that sometimes when records are entered out of order that the status is wrong. • So now we need to re-factor this query, but we also need to validate the output of the query to make sure we do not write a new bad query. CREATEVIEWpublic.view_bASSELECT a.id, a.name, b.status, b.action_dtFROMtable_a a LEFTJOIN ( SELECTDISTINCTON (table_b.a_id) table_b.id, table_b.a_id, table_b.status, table_b.action_dtFROMtable_bORDERBYtable_b.a_id, table_b.action_dtDESC ) b ON a.id =b.a_idWHEREb.status!='Left'ORDERBY a.id; pgTAP
Replace test (22) • Copy test 20 and update sql from using view_a to using view_b. • This will make sure with the basic data that we are still getting the same result with our re-factored view. SELECTresults_eq( 'SELECT id, name, status, action_dt FROM view_b;', $$VALUES (1,'Lloyd'::varchar,'Hired'::varchar,'2014-01-01'::timestamp), (2,'Judy','Normal','2016-05-05')$$, 'Verify Data in view_b'); pgTAP
Adding new test (23 & 24) • First we need to copy the second test for view_a and make it use view_b. • The we need to alter the second test for view_a to be a not equal test by using the results_ne command. • SELECT results_ne( :sql, :sql, :description ); • This will make sure with the more advanced test failed properly with view_a and works properly with view_b. SELECTresults_ne( 'SELECT id, name, status, action_dt FROM view_a;', $$VALUES (1,'Lloyd'::varchar,'Hired'::varchar,'2014-01-01'::timestamp), (2,'Judy','Normal','2016-10-01')$$, 'Verify Bad Data in view_a (2nd run)'); SELECTresults_eq( 'SELECT id, name, status, action_dt FROM view_b;', $$VALUES (1,'Lloyd'::varchar,'Hired'::varchar,'2014-01-01'::timestamp), (2,'Judy','Normal','2016-10-01')$$, 'Verify Good Data in view_b (2nd run)'); pgTAP