Given that the database, as the canonical repository of data, is the most important part of many applications, why is it that we don't write database unit tests? This talk promotes the practice of implementing tests to directly test the schema, storage, and functionality of databases.
35. “AHHHHHHHHH!!!! NOT
TESTING! Anything
but testing! Beat me,
whip me, send me to
Detroit, but don’t
make me write tests!”
—Michael Schwern, Test::Tutorial
42. Test Conceptions
For finding bugs
Difficult
Irrelevant
Time-consuming
For inexperienced
developers
Unnecessary for simple
code
43. Test Conceptions
For finding bugs Best for fragile code
Difficult
Irrelevant
Time-consuming
For inexperienced
developers
Unnecessary for simple
code
44. Test Conceptions
For finding bugs Best for fragile code
Difficult Users test the code
Irrelevant
Time-consuming
For inexperienced
developers
Unnecessary for simple
code
45. Test Conceptions
For finding bugs Best for fragile code
Difficult Users test the code
Irrelevant App tests are sufficient
Time-consuming
For inexperienced
developers
Unnecessary for simple
code
46. Test Conceptions
For finding bugs Best for fragile code
Difficult Users test the code
Irrelevant App tests are sufficient
Time-consuming For public interface only
For inexperienced
developers
Unnecessary for simple
code
47. Test Conceptions
For finding bugs Best for fragile code
Difficult Users test the code
Irrelevant App tests are sufficient
Time-consuming For public interface only
For inexperienced Prove nothing
developers
Unnecessary for simple
code
48. Test Conceptions
For finding bugs Best for fragile code
Difficult Users test the code
Irrelevant App tests are sufficient
Time-consuming For public interface only
For inexperienced Prove nothing
developers
For stable code
Unnecessary for simple
code
49. Test Conceptions
For finding bugs Best for fragile code
Difficult Users test the code
Irrelevant App tests are sufficient
Time-consuming For public interface only
For inexperienced Prove nothing
developers
For stable code
Unnecessary for simple
code I really like Detroit
57. Test-Driven Development
Say you need a Fibonacci Calculator
Start with a test
Write the simplest possible function
58. Test-Driven Development
Say you need a Fibonacci Calculator
Start with a test
Write the simplest possible function
Add more tests
59. Test-Driven Development
Say you need a Fibonacci Calculator
Start with a test
Write the simplest possible function
Add more tests
Update the function
60. Test-Driven Development
Say you need a Fibonacci Calculator
Start with a test
Write the simplest possible function
Add more tests
Update the function
Wash, rinse, repeat…
62. Simple Test
BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT * FROM finish();
ROLLBACK;
63. Simple Test
BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT * FROM finish();
ROLLBACK;
64. Simple Test
BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT * FROM finish();
ROLLBACK;
65. Simple Test
BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT * FROM finish();
ROLLBACK;
66. Simple Test
BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT * FROM finish();
ROLLBACK;
67. Simple Test
BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT * FROM finish();
ROLLBACK;
68. Simple Test
BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT * FROM finish();
ROLLBACK;
73. Run the Test
% psql -d try -f fib.sql
CREATE FUNCTION
% ❚
74. Run the Test
% psql -d try -f fib.sql
CREATE FUNCTION
%pg_prove -vd try test_fib.sql
test_fib.sql ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
1..2
ok
All tests successful.
Files=1, Tests=2, 0 secs (0.03 usr + 0.00 sys = 0.03 CPU)
Result: PASS
%❚
75. Run the Test
% psql -d try -f fib.sql
CREATE FUNCTION
%pg_prove -vd try test_fib.sql
test_fib.sql ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
1..2
ok
All tests successful.
Files=1, Tests=2, 0 secs (0.03 usr + 0.00 sys = 0.03 CPU)
Result: PASS
%❚
76. Run the Test
% psql -d try -f fib.sql
CREATE FUNCTION
%pg_prove -vd try test_fib.sql
test_fib.sql ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
1..2
ok
All tests successful.
Files=1, Tests=2, 0 secs (0.03 usr + 0.00 sys = 0.03 CPU)
Result: PASS
%❚
That was easy
82. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
ok 3 - fib(0) should be 0
not ok 4 - fib(1) should be 1
# Failed test 4: quot;fib(1) should be 1quot;
# have: 0
# want: 1
1..4
# Looks like you failed 1 test of 4
Failed 1/4 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 4 Failed: 1)
Failed test: 4
Files=1, Tests=4, 1 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
83. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
ok 3 - fib(0) should be 0
not ok 4 - fib(1) should be 1
# Failed test 4: quot;fib(1) should be 1quot;
# have: 0
# want: 1
1..4
# Looks like you failed 1 test of 4
Failed 1/4 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 4 Failed: 1)
Failed test: 4
Files=1, Tests=4, 1 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
84. Modify for the Test
CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
RETURN 0;
END;
$$ LANGUAGE plpgsql;
85. Modify for the Test
CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
RETURN fib_for;
END;
$$ LANGUAGE plpgsql;
86. Modify for the Test
CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
RETURN fib_for;
END;
$$ LANGUAGE plpgsql;
Bare minimum
88. Tests Pass!
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
ok 3 - fib(0) should be 0
ok 4 - fib(1) should be 1
1..4
ok
All tests successful.
Files=1, Tests=4, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: PASS
%❚
89. Add Another Assertion
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
90. Add Another Assertion
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
92. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sq1 ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
ok 3 - fib(0) should be 0
ok 4 - fib(1) should be 1
not ok 5 - fib(2) should be 1
# Failed test 5: quot;fib(2) should be 1quot;
# have: 2
# want: 1
1..5
# Looks like you failed 1 test of 5
Failed 1/5 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 5 Failed: 1)
Failed test: 5
Files=1, Tests=5, 1 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
93. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sq1 ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
ok 3 - fib(0) should be 0
ok 4 - fib(1) should be 1
not ok 5 - fib(2) should be 1
# Failed test 5: quot;fib(2) should be 1quot;
# have: 2
# want: 1
1..5
# Looks like you failed 1 test of 5
Failed 1/5 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 5 Failed: 1)
Failed test: 5
Files=1, Tests=5, 1 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
94. Modify to Pass
CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
RETURN fib_for;
END;
$$ LANGUAGE plpgsql;
95. Modify to Pass
CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
IF fib_for < 2 THEN
RETURN fib_for;
END IF;
RETURN fib_for - 1;
END;
$$ LANGUAGE plpgsql;
97. And…Pass!
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql ..
ok 1 - Schema pg_catalog or public or tap can
ok 2 - Function fib(integer) should exist
ok 3 - fib(0) should be 0
ok 4 - fib(1) should be 1
ok 5 - fib(2) should be 1
1..5
ok
All tests successful.
Files=1, Tests=5, 0 secs (0.02 usr + 0.00 sys = 0.02 CPU)
Result: PASS
%❚
98. Still More Assertions
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
99. Still More Assertions
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
SELECT is( fib(3), 2, 'fib(3) should be 2');
SELECT is( fib(4), 3, 'fib(4) should be 3');
SELECT is( fib(5), 5, 'fib(5) should be 5');
101. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sq1 .. 1/?
not ok 8 - fib(5) should be 5
# Failed test 8: quot;fib(5) should be 5quot;
# have: 4
# want: 5
# Looks like you failed 1 test of 8
test_fib.sql .. Failed 1/8 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 8 Failed: 1)
Failed test: 8
Files=1, Tests=8, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
102. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sq1 .. 1/?
not ok 8 - fib(5) should be 5
# Failed test 8: quot;fib(5) should be 5quot;
# have: 4
# want: 5
# Looks like you failed 1 test of 8
test_fib.sql .. Failed 1/8 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 8 Failed: 1)
Failed test: 8
Files=1, Tests=8, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
103. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sq1 .. 1/?
not ok 8 - fib(5) should be 5
# Failed test 8: quot;fib(5) should be 5quot;
# have: 4
# want: 5
# Looks like you failed 1 test of 8
test_fib.sql .. Failed 1/8 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 8 Failed: 1)
Failed test: 8
Files=1, Tests=8, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
104. Fix The Function
CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
IF fib_for < 2 THEN
RETURN fib_for;
END IF;
RETURN fib _for - 1;
END;
$$ LANGUAGE plpgsql;
105. Fix The Function
CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
IF fib_for < 2 THEN
RETURN fib_for;
END IF;
RETURN fib (fib_for - 2)
+ fib(fib_for - 1);
END;
$$ LANGUAGE plpgsql;
108. A Few More Assertions
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
SELECT is( fib(3), 2, 'fib(3) should be 2');
SELECT is( fib(4), 3, 'fib(4) should be 3');
SELECT is( fib(5), 5, 'fib(5) should be 5');
109. A Few More Assertions
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
SELECT is( fib(3), 2, 'fib(3) should be 2');
SELECT is( fib(4), 3, 'fib(4) should be 3');
SELECT is( fib(5), 5, 'fib(5) should be 5');
SELECT is( fib(6), 8, 'fib(6) should be 8');
SELECT is( fib(7), 13, 'fib(7) should be 13');
SELECT is( fib(8), 21, 'fib(8) should be 21');
127. Add a Regression Test
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
SELECT is( fib(3), 2, 'fib(3) should be 2');
SELECT is( fib(4), 3, 'fib(4) should be 3');
SELECT is( fib(5), 5, 'fib(5) should be 5');
SELECT is( fib(6), 8, 'fib(6) should be 8');
SELECT is( fib(7), 13, 'fib(7) should be 13');
SELECT is( fib(8), 21, 'fib(8) should be 21');
128. Add a Regression Test
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
SELECT is( fib(3), 2, 'fib(3) should be 2');
SELECT is( fib(4), 3, 'fib(4) should be 3');
SELECT is( fib(5), 5, 'fib(5) should be 5');
SELECT is( fib(6), 8, 'fib(6) should be 8');
SELECT is( fib(7), 13, 'fib(7) should be 13');
SELECT is( fib(8), 21, 'fib(8) should be 21');
SELECT performs_ok( 'SELECT fib(30)', 500 );
130. What’ve We Got?
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql .. 12/?
not ok 12 - Should run in less than 500 ms
# Failed test 12: quot;Should run in less than 500 msquot;
# runtime: 8418.816 ms
# exceeds: 500 ms
# Looks like you failed 1 test of 12
test_fib.sql .. Failed 1/12 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 12 Failed: 1)
Failed test: 12
Files=1, Tests=12, 8 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
131. What’ve We Got?
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql .. 12/?
not ok 12 - Should run in less than 500 ms
# Failed test 12: quot;Should run in less than 500 msquot;
# runtime: 8418.816 ms
# exceeds: 500 ms
# Looks like you failed 1 test of 12
test_fib.sql .. Failed 1/12 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 12 Failed: 1)
Failed test: 12
Files=1, Tests=12, 8 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
134. CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
DECLARE
ret integer := 0;
nxt integer := 1;
tmp integer;
BEGIN
FOR num IN 0..fib_for LOOP
tmp := ret;
ret := nxt;
nxt := tmp + nxt;
END LOOP;
RETURN ret;
END;
$$ LANGUAGE plpgsql;
136. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sq1 .. 1/?
not ok 3 - fib(0) should be 0
# Failed test 3: quot;fib(0) should be 0quot;
# have: 1
# want: 0
not ok 5 - fib(2) should be 1
# Failed test 5: quot;fib(2) should be 1quot;
# have: 2
# want: 1
not ok 6 - fib(3) should be 2
# Failed test 6: quot;fib(3) should be 2quot;
# have: 3
# want: 2
not ok 7 - fib(4) should be 3
# Failed test 7: quot;fib(4) should be 3quot;
# have: 5
# want: 3
not ok 8 - fib(5) should be 5
# Failed test 8: quot;fib(5) should be 5quot;
# have: 8
# want: 5
not ok 9 - fib(6) Should be 8
# Failed test 9: quot;fib(6) Should be 8quot;
# have: 13
# want: 8
not ok 10 - fib(7) Should be 13
# Failed test 10: quot;fib(7) Should be 13quot;
# have: 21
# want: 13
not ok 11 - fib(8) Should be 21
# Failed test 11: quot;fib(8) Should be 21quot;
# have: 34
# want: 21
# Looks like you failed 8 tests of 12
test_fib.sql .. Failed 8/12 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 12 Failed: 8)
Failed tests: 3, 5-11
Files=1, Tests=12, 0 secs (0.03 usr + 0.01 sys = 0.04 CPU)
Result: FAIL
137. % psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sq1 .. 1/?
not ok 3 - fib(0) should be 0
# Failed test 3: quot;fib(0) should be 0quot;
# have: 1
WTF?
# want: 0
not ok 5 - fib(2) should be 1
# Failed test 5: quot;fib(2) should be 1quot;
# have: 2
# want: 1
not ok 6 - fib(3) should be 2
# Failed test 6: quot;fib(3) should be 2quot;
# have: 3
# want: 2
not ok 7 - fib(4) should be 3
# Failed test 7: quot;fib(4) should be 3quot;
# have: 5
# want: 3
not ok 8 - fib(5) should be 5
# Failed test 8: quot;fib(5) should be 5quot;
# have: 8
# want: 5
not ok 9 - fib(6) Should be 8
# Failed test 9: quot;fib(6) Should be 8quot;
# have: 13
# want: 8
not ok 10 - fib(7) Should be 13
# Failed test 10: quot;fib(7) Should be 13quot;
# have: 21
# want: 13
not ok 11 - fib(8) Should be 21
# Failed test 11: quot;fib(8) Should be 21quot;
# have: 34
# want: 21
# Looks like you failed 8 tests of 12
test_fib.sql .. Failed 8/12 subtests
Test Summary Report
-------------------
test_fib.sql (Wstat: 0 Tests: 12 Failed: 8)
Failed tests: 3, 5-11
Files=1, Tests=12, 0 secs (0.03 usr + 0.01 sys = 0.04 CPU)
Result: FAIL
138. CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
DECLARE
ret integer := 0;
nxt integer := 1;
tmp integer;
BEGIN
0
FOR num IN ..fib_for LOOP
tmp := ret;
ret := nxt;
nxt := tmp + nxt;
END LOOP;
RETURN ret;
END;
$$ LANGUAGE plpgsql;
139. CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer AS $$
BEGIN
DECLARE
ret integer := 0;
nxt integer := 1;
tmp integer;
BEGIN
1
FOR num IN ..fib_for LOOP
tmp := ret;
ret := nxt;
nxt := tmp + nxt;
END LOOP;
RETURN ret;
END;
$$ LANGUAGE plpgsql;
145. Push It!
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
SELECT is( fib(3), 2, 'fib(3) should be 2');
SELECT is( fib(4), 3, 'fib(4) should be 3');
SELECT is( fib(5), 5, 'fib(5) should be 5');
SELECT is( fib(6), 8, 'fib(6) should be 8');
SELECT is( fib(7), 13, 'fib(7) should be 13');
SELECT is( fib(8), 21, 'fib(8) should be 21');
SELECT performs_ok( 'SELECT fib(30)', 500 );
146. Push It!
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
SELECT is( fib(3), 2, 'fib(3) should be 2');
SELECT is( fib(4), 3, 'fib(4) should be 3');
SELECT is( fib(5), 5, 'fib(5) should be 5');
SELECT is( fib(6), 8, 'fib(6) should be 8');
SELECT is( fib(7), 13, 'fib(7) should be 13');
SELECT is( fib(8), 21, 'fib(8) should be 21');
SELECT performs_ok( 'SELECT fib(30)', 500 );
SELECT is( fib(32), 2178309, 'fib(32) is 2178309');
SELECT is( fib(64), 10610209857723, 'fib(64) is 10610209857723');
148. No Fibbing.
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql .. 1/? psql:test_fib.sql:18: ERROR: function is(integer, bigint,
unknown) does not exist
LINE 1: SELECT is( fib(64), 10610209857723, 'fib(64) Should be 10610...
^
HINT: No function matches the given name and argument types. You might need
to add explicit type casts.
test_fib.sql .. Dubious, test returned 3 (wstat 768, 0x300)
All 13 subtests passed
Test Summary Report
-------------------
test_fib.sql (Wstat: 768 Tests: 13 Failed: 0)
Non-zero exit status: 3
Parse errors: No plan found in TAP output
Files=1, Tests=13, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
149. No Fibbing.
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql .. 1/? psql:test_fib.sql:18: ERROR: function is(integer, bigint,
unknown) does not exist
LINE 1: SELECT is( fib(64), 10610209857723, 'fib(64) Should be 10610...
^
HINT: No function matches the given name and argument types. You might need
to add explicit type casts.
test_fib.sql .. Dubious, test returned 3 (wstat 768, 0x300)
All 13 subtests passed
Hrm…
Test Summary Report
-------------------
test_fib.sql (Wstat: 768 Tests: 13 Failed: 0)
Non-zero exit status: 3
Parse errors: No plan found in TAP output
Files=1, Tests=13, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: FAIL
%❚
150. CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS integer $$
AS
BEGIN
DECLARE
ret integer
:= 0;
nxt integer
:= 1;
tmp integer;
BEGIN
FOR num IN 1..fib_for LOOP
tmp := ret;
ret := nxt;
nxt := tmp + nxt;
END LOOP;
RETURN ret;
END;
$$ LANGUAGE plpgsql;
151. CREATE OR REPLACE FUNCTION fib (
fib_for integer
) RETURNS bigint $$AS
BEGIN
DECLARE
ret bigint
:= 0;
nxt bigint1;
:=
tmp bigint ;
BEGIN
FOR num IN 1..fib_for LOOP
tmp := ret;
ret := nxt;
nxt := tmp + nxt;
END LOOP;
RETURN ret;
END;
$$ LANGUAGE plpgsql;
152. Apples to Apples…
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0, 'fib(0) should be 0');
SELECT is( fib(1), 1, 'fib(1) should be 1');
SELECT is( fib(2), 1, 'fib(2) should be 1');
SELECT is( fib(3), 2, 'fib(3) should be 2');
SELECT is( fib(4), 3, 'fib(4) should be 3');
SELECT is( fib(5), 5, 'fib(5) should be 5');
SELECT is( fib(6), 8, 'fib(6) should be 8');
SELECT is( fib(7), 13, 'fib(7) should be 13');
SELECT is( fib(8), 21, 'fib(8) should be 21');
SELECT performs_ok( 'SELECT fib(30)', 500 );
SELECT is( fib(32), 2178309, 'fib(32) is 2178309');
SELECT is( fib(64), 10610209857723, 'fib(64) is 10610209857723');
153. Apples to Apples…
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
SELECT is( fib(0), 0::int8, 'fib(0) should be 0');
SELECT is( fib(1), 1::int8, 'fib(1) should be 1');
SELECT is( fib(2), 1::int8, 'fib(2) should be 1');
SELECT is( fib(3), 2::int8, 'fib(3) should be 2');
SELECT is( fib(4), 3::int8, 'fib(4) should be 3');
SELECT is( fib(5), 5::int8, 'fib(5) should be 5');
SELECT is( fib(6), 8::int8, 'fib(6) should be 8');
SELECT is( fib(7), 13::int8, 'fib(7) should be 13');
SELECT is( fib(8), 21::int8, 'fib(8) should be 21');
SELECT performs_ok( 'SELECT fib(30)', 500 );
SELECT is( fib(32), 2178309::int8, 'fib(32) is 2178309');
SELECT is( fib(64), 10610209857723::int8, 'fib(64) is 10610209857723');
161. CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
p_day ALIAS FOR $1;
p_mon ALIAS FOR $2;
p_offset ALIAS FOR $3;
p_limit ALIAS FOR $4;
p_state ALIAS FOR $5;
v_qry TEXT;
v_output RECORD;
BEGIN
v_qry := 'SELECT * FROM users WHERE state = ''' || p_state || '''';
v_qry := v_qry || ' AND birth_mon ~ ''^0?' || p_mon::text || '$''';
v_qry := v_qry || ' AND birth_day = ''' || p_day::text || '''';
v_qry := v_qry || ' ORDER BY user_id';
IF p_offset IS NOT NULL THEN
v_qry := v_qry || ' OFFSET ' || p_offset;
END IF;
IF p_limit IS NOT NULL THEN
v_qry := v_qry || ' LIMIT ' || p_limit;
END IF;
FOR v_output IN EXECUTE v_qry LOOP
RETURN NEXT v_output.user_id;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
162. CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
p_day ALIAS FOR $1;
p_mon ALIAS FOR $2;
p_offset ALIAS FOR $3;
p_limit ALIAS FOR $4;
p_state ALIAS FOR $5;
v_qry TEXT;
v_output RECORD;
BEGIN
v_qry := 'SELECT * FROM users WHERE state = ''' || p_state || '''';
v_qry := v_qry || ' AND birth_mon ~ ''^0?' || p_mon::text || '$''';
v_qry := v_qry || ' AND birth_day = ''' || p_day::text || '''';
v_qry := v_qry || ' ORDER BY user_id';
IF p_offset IS NOT NULL THEN
v_qry := v_qry || ' OFFSET ' || p_offset;
END IF;
IF p_limit IS NOT NULL THEN
v_qry := v_qry || ' LIMIT ' || p_limit;
END IF;
FOR v_output IN EXECUTE v_qry LOOP
RETURN NEXT v_output.user_id;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
163. CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
p_day ALIAS FOR $1;
p_mon ALIAS FOR $2;
p_offset ALIAS FOR $3;
p_limit ALIAS FOR $4;
p_state ALIAS FOR $5;
v_qry TEXT;
v_output RECORD;
BEGIN
v_qry := 'SELECT * FROM users WHERE state = ''' || p_state || '''';
v_qry := v_qry || ' AND birth_mon ~ ''^0?' || p_mon::text || '$''';
v_qry := v_qry || ' AND birth_day = ''' || p_day::text || '''';
v_qry := v_qry || ' ORDER BY user_id';
IF p_offset IS NOT NULL THEN
v_qry := v_qry || ' OFFSET ' || p_offset;
END IF;
IF p_limit IS NOT NULL THEN
v_qry := v_qry || ' LIMIT ' || p_limit;
END IF;
FOR v_output IN EXECUTE v_qry LOOP
RETURN NEXT v_output.user_id;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
164. CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
p_day ALIAS FOR $1;
p_mon ALIAS FOR $2;
p_offset ALIAS FOR $3;
p_limit ALIAS FOR $4;
p_state ALIAS FOR $5;
v_qry TEXT;
v_output RECORD;
BEGIN
v_qry := 'SELECT * FROM users WHERE state = ''' || p_state || '''';
v_qry := v_qry || ' AND birth_mon ~ ''^0?' || p_mon::text || '$''';
v_qry := v_qry || ' AND birth_day = ''' || p_day::text || '''';
v_qry := v_qry || ' ORDER BY user_id';
IF p_offset IS NOT NULL THEN
v_qry := v_qry || ' OFFSET ' || p_offset;
END IF;
IF p_limit IS NOT NULL THEN
v_qry := v_qry || ' LIMIT ' || p_limit;
END IF;
FOR v_output IN EXECUTE v_qry LOOP
RETURN NEXT v_output.user_id;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
165. CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
p_day ALIAS FOR $1;
p_mon ALIAS FOR $2;
p_offset ALIAS FOR $3;
p_limit ALIAS FOR $4;
p_state ALIAS FOR $5;
v_qry TEXT;
v_output RECORD;
BEGIN
v_qry := 'SELECT * FROM users WHERE state = ''' || p_state || '''';
v_qry := v_qry || ' AND birth_mon ~ ''^0?' || p_mon::text || '$''';
v_qry := v_qry || ' AND birth_day = ''' || p_day::text || '''';
v_qry := v_qry || ' ORDER BY user_id';
IF p_offset IS NOT NULL THEN
v_qry := v_qry || ' OFFSET ' || p_offset;
END IF;
IF p_limit IS NOT NULL THEN
v_qry := v_qry || ' LIMIT ' || p_limit;
END IF;
FOR v_output IN EXECUTE v_qry LOOP
RETURN NEXT v_output.user_id;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
166. CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
p_day ALIAS FOR $1;
p_mon ALIAS FOR $2;
p_offset ALIAS FOR $3;
p_limit ALIAS FOR $4;
p_state ALIAS FOR $5;
v_qry TEXT;
v_output RECORD;
BEGIN
v_qry := 'SELECT * FROM users WHERE state = ''' || p_state || '''';
v_qry := v_qry || ' AND birth_mon ~ ''^0?' || p_mon::text || '$''';
v_qry := v_qry || ' AND birth_day = ''' || p_day::text || '''';
v_qry := v_qry || ' ORDER BY user_id';
IF p_offset IS NOT NULL THEN
v_qry := v_qry || ' OFFSET ' || p_offset;
END IF;
IF p_limit IS NOT NULL THEN
v_qry := v_qry || ' LIMIT ' || p_limit;
END IF;
FOR v_output IN EXECUTE v_qry LOOP
RETURN NEXT v_output.user_id;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
168. What’s on the Table?
try=# users
d
Table quot;public.usersquot;
Column | Type | Modifiers
------------
+-------------------------------------------------------
user_id | integer | not null default nextval(…)
name | text | not null default ''::text
birthdate | date |
birth_mon | character varying(2) |
birth_day | character varying(2) |
birth_year | character varying(4) |
state | text | not null default 'active'::text
Indexes:
quot;users_pkeyquot; PRIMARY KEY, btree (user_id)
169. What’s on the Table?
try=# users
d
Table quot;public.usersquot;
Column | Type | Modifiers
------------
+-------------------------------------------------------
user_id | integer | not null default nextval(…)
name | text | not null default ''::text
birthdate | date |
birth_mon | character varying(2) |
birth_day | character varying(2) |
birth_year | character varying(4) |
state | text | not null default 'active'::text
Indexes:
quot;users_pkeyquot; PRIMARY KEY, btree (user_id)
218. Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
p_day integer,
p_mon integer,
p_offset integer,
p_limit integer,
p_state text
) RETURNS SETOF integer AS $$
SELECT user_id
FROM users
WHERE state = COALESCE($5, 'active')
AND EXTRACT(day FROM birthdate) = $1
AND EXTRACT(month FROM birthdate) = $2
ORDER BY user_id
OFFSET COALESCE( $3, NULL )
LIMIT COALESCE( $4, NULL )
$$ LANGUAGE sql;
219. Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
p_day integer,
p_mon integer,
p_offset integer,
p_limit integer,
p_state text
) RETURNS SETOF integer AS $$
SELECT user_id
FROM users
WHERE state = COALESCE($5, 'active')
AND EXTRACT(day FROM birthdate) = $1
AND EXTRACT(month FROM birthdate) = $2
ORDER BY user_id
OFFSET COALESCE( $3, NULL )
LIMIT COALESCE( $4, NULL )
$$ LANGUAGE sql;
220. Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
p_day integer,
p_mon integer,
p_offset integer,
p_limit integer,
p_state text
) RETURNS SETOF integer AS $$
SELECT user_id
FROM users
WHERE state = COALESCE($5, 'active')
AND EXTRACT(day FROM birthdate) = $1
AND EXTRACT(month FROM birthdate) = $2
ORDER BY user_id
OFFSET COALESCE( $3, NULL )
LIMIT COALESCE( $4, NULL )
$$ LANGUAGE sql;
221. Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
p_day integer,
p_mon integer,
p_offset integer,
p_limit integer,
p_state text
) RETURNS SETOF integer AS $$
SELECT user_id
FROM users
WHERE state = COALESCE($5, 'active')
AND EXTRACT(day FROM birthdate) = $1
AND EXTRACT(month FROM birthdate) = $2
ORDER BY user_id
OFFSET COALESCE( $3, NULL )
LIMIT COALESCE( $4, NULL )
$$ LANGUAGE sql;
222. Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
p_day integer,
p_mon integer,
p_offset integer,
p_limit integer,
p_state text
) RETURNS SETOF integer AS $$
SELECT user_id
FROM users
WHERE state = COALESCE($5, 'active')
AND EXTRACT(day FROM birthdate) = $1
AND EXTRACT(month FROM birthdate) = $2
ORDER BY user_id
OFFSET COALESCE( $3, NULL )
LIMIT COALESCE( $4, NULL )
$$ LANGUAGE sql;
236. Tests are Hard
Good frameworks easy
pgTAP provides lots of assertions
If you mean Hard to test interface:
237. Tests are Hard
Good frameworks easy
pgTAP provides lots of assertions
If you mean Hard to test interface:
Red flag
238. Tests are Hard
Good frameworks easy
pgTAP provides lots of assertions
If you mean Hard to test interface:
Red flag
Think about refactoring
239. Tests are Hard
Good frameworks easy
pgTAP provides lots of assertions
If you mean Hard to test interface:
Red flag
Think about refactoring
If it’s hard to test…
240. Tests are Hard
Good frameworks easy
pgTAP provides lots of assertions
If you mean Hard to test interface:
Red flag
Think about refactoring
If it’s hard to test…
It’s hard to use
250. Time-Consuming
Good frameworks easy to use
Iterating between tests and code is natural
Tests are as fast as your code
Not as time-consuming as bug hunting
251. Time-Consuming
Good frameworks easy to use
Iterating between tests and code is natural
Tests are as fast as your code
Not as time-consuming as bug hunting
When no tests, bugs repeat themselves
252. Time-Consuming
Good frameworks easy to use
Iterating between tests and code is natural
Tests are as fast as your code
Not as time-consuming as bug hunting
When no tests, bugs repeat themselves
And are harder to track down
253. Time-Consuming
Good frameworks easy to use
Iterating between tests and code is natural
Tests are as fast as your code
Not as time-consuming as bug hunting
When no tests, bugs repeat themselves
And are harder to track down
Talk about a time sink!
260. For Inexperienced
Developers
I’ve been programming for 10 years
I have no idea what I was thinking a year ago
261. For Inexperienced
Developers
I’ve been programming for 10 years
I have no idea what I was thinking a year ago
Tests make maintenance a breeze
262. For Inexperienced
Developers
I’ve been programming for 10 years
I have no idea what I was thinking a year ago
Tests make maintenance a breeze
They give me the confidence to make changes
without fearing the consequences
263. For Inexperienced
Developers
I’ve been programming for 10 years
I have no idea what I was thinking a year ago
Tests make maintenance a breeze
They give me the confidence to make changes
without fearing the consequences
Tests represent FREEDOM from the tyranny
of fragility and inconsistency
271. Best for Fragile Code
All code is fragile
Tests make code ROBUST
272. Best for Fragile Code
All code is fragile
Tests make code ROBUST
Add regression tests for bugs found by
273. Best for Fragile Code
All code is fragile
Tests make code ROBUST
Add regression tests for bugs found by
Integration tests
274. Best for Fragile Code
All code is fragile
Tests make code ROBUST
Add regression tests for bugs found by
Integration tests
QA department
275. Best for Fragile Code
All code is fragile
Tests make code ROBUST
Add regression tests for bugs found by
Integration tests
QA department
Your users
278. Users Test our Code
Talk about fragility
Staging servers never work
279. Users Test our Code
Talk about fragility
Staging servers never work
QA departments are disappearing
280. Users Test our Code
Talk about fragility
Staging servers never work
QA departments are disappearing
Users don’t want to see bugs
281. Users Test our Code
Talk about fragility
Staging servers never work
QA departments are disappearing
Users don’t want to see bugs
Find ways to test your code
282. Users Test our Code
Talk about fragility
Staging servers never work
QA departments are disappearing
Users don’t want to see bugs
Find ways to test your code
Users avoid fragile applications
290. Application Tests are
Sufficient
App tests should connect as as app user
May well be security limitations for the app
291. Application Tests are
Sufficient
App tests should connect as as app user
May well be security limitations for the app
Access only to functions
292. Application Tests are
Sufficient
App tests should connect as as app user
May well be security limitations for the app
Access only to functions
Apps cannot adequately test the database
293. Application Tests are
Sufficient
App tests should connect as as app user
May well be security limitations for the app
Access only to functions
Apps cannot adequately test the database
Database tests should test the database
294. Application Tests are
Sufficient
App tests should connect as as app user
May well be security limitations for the app
Access only to functions
Apps cannot adequately test the database
Database tests should test the database
Application tests should test the application
300. Tests Prove Nothing
This is not a math equation
This is about:
consistency
stability
robusticity
301. Tests Prove Nothing
This is not a math equation
This is about:
consistency
stability
robusticity
If a test fails, it has proved a failure
302. Tests Prove Nothing
This is not a math equation
This is about:
consistency
stability
robusticity
If a test fails, it has proved a failure
Think Karl Popper
304. Tests are for Stable Code
How does it become stable?
305. Tests are for Stable Code
How does it become stable?
Tests the fastest route
306. Tests are for Stable Code
How does it become stable?
Tests the fastest route
Ensure greater stability over time
307. Tests are for Stable Code
How does it become stable?
Tests the fastest route
Ensure greater stability over time
TDD help with working through issues
308. Tests are for Stable Code
How does it become stable?
Tests the fastest route
Ensure greater stability over time
TDD help with working through issues
TDD helps thinking through interfaces
309. Tests are for Stable Code
How does it become stable?
Tests the fastest route
Ensure greater stability over time
TDD help with working through issues
TDD helps thinking through interfaces
Tests encourage experimentation