SlideShare una empresa de Scribd logo
1 de 331
Unit Test Your Database!
         David E. Wheeler
      PostgreSQL Experts, Inc.

      PGCon, May 21, 2009
Why?
Do these
sound familiar?
“It takes too long
 to write tests.”
“Testing will just
 slow me down.”
“It takes too long
 to run tests.”
“We already write
 app-level unit tests.”
“I test stuff by
 running my app.”
“Tests never find
 relevant bugs.”
“This code is so simple
 it doesn’t need tests.”
“This function is
 too hard to test.”
“This is a private
 function.”
“Tests can't prove a
 program correct
 so why bother?”
“The behavior of the
 code changes a lot
 and rewriting the
 tests to match will
 just slow things
 down.”
“If I imagined a
 problem to write
 tests for, I probably
 wrote code that
 doesn’t have that
 problem.”
“I can produce
 software that works
 even without focusing
 specifically on low-
 level unit tests.”
“I’m lucky enough
 to only be dealing
 with really good
 developers.”
“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
Test Conceptions
Test Conceptions
For finding bugs
Test Conceptions
For finding bugs

Difficult
Test Conceptions
For finding bugs

Difficult

Irrelevant
Test Conceptions
For finding bugs

Difficult

Irrelevant

Time-consuming
Test Conceptions
For finding bugs

Difficult

Irrelevant

Time-consuming

For inexperienced
developers
Test Conceptions
For finding bugs

Difficult

Irrelevant

Time-consuming

For inexperienced
developers

Unnecessary for simple
code
Test Conceptions
For finding bugs          Best for fragile code

Difficult

Irrelevant

Time-consuming

For inexperienced
developers

Unnecessary for simple
code
Test Conceptions
For finding bugs          Best for fragile code

Difficult                 Users test the code

Irrelevant

Time-consuming

For inexperienced
developers

Unnecessary for simple
code
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
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
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
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
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
Let’s
Get Real
What does
 it take?
Test-Driven Development
Test-Driven Development
 Say you need a Fibonacci Calculator
Test-Driven Development
 Say you need a Fibonacci Calculator

 Start with a test
Test-Driven Development
 Say you need a Fibonacci Calculator

 Start with a test

 Write the simplest possible function
Test-Driven Development
 Say you need a Fibonacci Calculator

 Start with a test

 Write the simplest possible function

 Add more tests
Test-Driven Development
 Say you need a Fibonacci Calculator

 Start with a test

 Write the simplest possible function

 Add more tests

 Update the function
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…
Simple Test
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;
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;
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;
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;
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;
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;
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;
Simple Function
Simple Function

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
   RETURN 0;
END;
$$ LANGUAGE plpgsql;
Simple Function

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
   RETURN 0;
END;
$$ LANGUAGE plpgsql;
Run the Test
%
Run the Test
% psql -d try -f fib.sql
CREATE FUNCTION
% ❚
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
%❚
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
%❚
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
Add Assertions
SELECT can('{fib}');
SELECT can_ok('fib', ARRAY['integer']);
Add 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');
Add 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');
Add 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');
%
% 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
%❚
% 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
%❚
Modify for the Test

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
   RETURN 0;
END;
$$ LANGUAGE plpgsql;
Modify for the Test

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
   RETURN fib_for;
END;
$$ LANGUAGE plpgsql;
Modify for the Test

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
   RETURN fib_for;
END;
$$ LANGUAGE plpgsql;


      Bare minimum
Tests Pass!
%
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
%❚
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');
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');
%
% 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
%❚
% 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
%❚
Modify to Pass

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
     RETURN fib_for;
END;
$$ LANGUAGE plpgsql;
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;
And…Pass!
%
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
%❚
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');
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');
%
% 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
%❚
% 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
%❚
% 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
%❚
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;
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;
W00T!
%
W00T!
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql .. ok
All tests successful.
Files=1, Tests=8, 0 secs (0.02 usr + 0.00 sys = 0.02 CPU)
Result: PASS
%❚
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');
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');
We’re Golden!
%
We’re Golden!
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql .. ok
All tests successful.
Files=1, Tests=11, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: PASS
%❚
Make it so,
Number One.
OMG
WTF???
The server is
hammered!
Debug,
debug,
debug…
Nailed it!
Detroit, we have a
            problem.
try=#
Detroit, we have a
               problem.
try=#
    timing
Timing is on.
try=# select fib(30);

  fib
--------
 832040
(1 row)

Time: 6752.112 ms
try=# ❚
Detroit, we have a
               problem.
try=#
    timing
Timing is on.
try=# select fib(30);

  fib
--------
 832040
(1 row)

Time: 6752.112 ms
try=# ❚
Regression
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');
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 );
What’ve We Got?
%
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
%❚
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
%❚
Refactor
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;
%
% 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
% 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
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;
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;
Back in Business
%
Back in Business
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql .. ok
All tests successful.
Files=1, Tests=12, 1 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: PASS
%❚
⌀
Just for the
 Hell of it…
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 );
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');
No Fibbing.
%
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

%❚
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

%❚
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;
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;
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');
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');
And Now?
%
And Now?
% psql -d try -f fib.sql
CREATE FUNCTION
% pg_prove -vd try test_fib.sql
test_fib.sql .. ok
All tests successful.
Files=1, Tests=14, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: PASS
%❚
TDD Means
Consistency
What about
Maintenance?
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;
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;
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;
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;
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;
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;
What’s on the Table?
try=#
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)
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)
What’s on the Table?
try=#
What’s on the Table?
try=#
    select * from users;
 user_id | name | birthdate | birth_mon | birth_day | birth_year | state
---------+-------+------------+-----------+-----------+------------
+--------
     1 | David | 1968-12-19 | 12      | 19      | 1968      | active
     2 | Josh | 1970-03-12 | 03      | 12      | 1970      | active
     3 | Dan | 1972-06-03 | 6        |3       | 1972      | active
(3 rows)
What’s on the Table?
try=#
    select * from users;
 user_id | name | birthdate | birth_mon | birth_day | birth_year | state
---------+-------+------------+-----------+-----------+------------
+--------
     1 | David | 1968-12-19 | 12      | 19      | 1968      | active
     2 | Josh | 1970-03-12 | 03      | 12      | 1970      | active
     3 | Dan | 1972-06-03 | 6        |3       | 1972      | active
(3 rows)
Must…
restrain…
fist…
of death…
The Situation
The Situation
This is production code
The Situation
This is production code

Cannot afford downtime
The Situation
This is production code

Cannot afford downtime

No room for mistakes
The Situation
This is production code

Cannot afford downtime

No room for mistakes

Bugs must remain consistent
The Situation
This is production code

Cannot afford downtime

No room for mistakes

Bugs must remain consistent

But…
Dear GOD it
needs rewriting.
But first…
Test the existing
implementation.
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

SELECT has_table( 'users' );
SELECT has_pk( 'users' );

SELECT   has_column( 'users', 'user_id' );
SELECT   col_type_is( 'users', 'user_id', 'integer' );
SELECT   col_is_pk( 'users', 'user_id' );
SELECT   col_not_null( 'users', 'user_id' );

SELECT has_column( 'users', 'birthdate' );
SELECT col_type_is( 'users', 'birthdate', 'date' );
SELECT col_is_null( 'users', 'birthdate' );

SELECT   has_column(      'users', 'state' );
SELECT   col_type_is( 'users', 'state', 'text' );
SELECT   col_not_null( 'users', 'state' );
SELECT   col_default_is( 'users', 'state', 'active' );

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

SELECT has_table( 'users' );
SELECT has_pk( 'users' );

SELECT   has_column( 'users', 'user_id' );
SELECT   col_type_is( 'users', 'user_id', 'integer' );
SELECT   col_is_pk( 'users', 'user_id' );
SELECT   col_not_null( 'users', 'user_id' );

SELECT has_column( 'users', 'birthdate' );
SELECT col_type_is( 'users', 'birthdate', 'date' );
SELECT col_is_null( 'users', 'birthdate' );

SELECT   has_column(      'users', 'state' );
SELECT   col_type_is( 'users', 'state', 'text' );
SELECT   col_not_null( 'users', 'state' );
SELECT   col_default_is( 'users', 'state', 'active' );

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

SELECT has_table( 'users' );
SELECT has_pk( 'users' );

SELECT   has_column( 'users', 'user_id' );
SELECT   col_type_is( 'users', 'user_id', 'integer' );
SELECT   col_is_pk( 'users', 'user_id' );
SELECT   col_not_null( 'users', 'user_id' );

SELECT has_column( 'users', 'birthdate' );
SELECT col_type_is( 'users', 'birthdate', 'date' );
SELECT col_is_null( 'users', 'birthdate' );

SELECT   has_column(      'users', 'state' );
SELECT   col_type_is( 'users', 'state', 'text' );
SELECT   col_not_null( 'users', 'state' );
SELECT   col_default_is( 'users', 'state', 'active' );

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

SELECT has_table( 'users' );
SELECT has_pk( 'users' );

SELECT   has_column( 'users', 'user_id' );
SELECT   col_type_is( 'users', 'user_id', 'integer' );
SELECT   col_is_pk( 'users', 'user_id' );
SELECT   col_not_null( 'users', 'user_id' );

SELECT has_column( 'users', 'birthdate' );
SELECT col_type_is( 'users', 'birthdate', 'date' );
SELECT col_is_null( 'users', 'birthdate' );

SELECT   has_column(      'users', 'state' );
SELECT   col_type_is( 'users', 'state', 'text' );
SELECT   col_not_null( 'users', 'state' );
SELECT   col_default_is( 'users', 'state', 'active' );

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

SELECT has_table( 'users' );
SELECT has_pk( 'users' );

SELECT   has_column( 'users', 'user_id' );
SELECT   col_type_is( 'users', 'user_id', 'integer' );
SELECT   col_is_pk( 'users', 'user_id' );
SELECT   col_not_null( 'users', 'user_id' );

SELECT has_column( 'users', 'birthdate' );
SELECT col_type_is( 'users', 'birthdate', 'date' );
SELECT col_is_null( 'users', 'birthdate' );

SELECT   has_column(      'users', 'state' );
SELECT   col_type_is( 'users', 'state', 'text' );
SELECT   col_not_null( 'users', 'state' );
SELECT   col_default_is( 'users', 'state', 'active' );

SELECT * FROM finish();
ROLLBACK;
Schema Sanity
%
Schema Sanity
% pg_prove -d try test_schema.sql
test_schema.sql .. ok
All tests successful.
Files=1, Tests=13, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: PASS
%❚
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELECT can_ok(
   'find_by_birthday',
   ARRAY['integer', 'integer', 'integer', 'integer', 'text']
);

-- Set up fixtures.
ALTER SEQUENCE users_user_id_seq RESTART 1;
INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year)
VALUES ('David', '1968-12-19', '12', '19', '1968'),
    ('Josh', '1970-03-12', '03', '12', '1970'),
    ('Dan', '1972-06-03', '6', '3', '1972'),
    ('Anna', '2005-06-03', '06', '3', '2005');

SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELECT can_ok(
   'find_by_birthday',
   ARRAY['integer', 'integer', 'integer', 'integer', 'text']
);

-- Set up fixtures.
ALTER SEQUENCE users_user_id_seq RESTART 1;
INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year)
VALUES ('David', '1968-12-19', '12', '19', '1968'),
    ('Josh', '1970-03-12', '03', '12', '1970'),
    ('Dan', '1972-06-03', '6', '3', '1972'),
    ('Anna', '2005-06-03', '06', '3', '2005');

SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELECT can_ok(
   'find_by_birthday',
   ARRAY['integer', 'integer', 'integer', 'integer', 'text']
);

-- Set up fixtures.
ALTER SEQUENCE users_user_id_seq RESTART 1;
INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year)
VALUES ('David', '1968-12-19', '12', '19', '1968'),
    ('Josh', '1970-03-12', '03', '12', '1970'),
    ('Dan', '1972-06-03', '6', '3', '1972'),
    ('Anna', '2005-06-03', '06', '3', '2005');

SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELECT can_ok(
   'find_by_birthday',
   ARRAY['integer', 'integer', 'integer', 'integer', 'text']
);

-- Set up fixtures.
ALTER SEQUENCE users_user_id_seq RESTART 1;
INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year)
VALUES ('David', '1968-12-19', '12', '19', '1968'),
    ('Josh', '1970-03-12', '03', '12', '1970'),
    ('Dan', '1972-06-03', '6', '3', '1972'),
    ('Anna', '2005-06-03', '06', '3', '2005');

SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELECT can_ok(
   'find_by_birthday',
   ARRAY['integer', 'integer', 'integer', 'integer', 'text']
);

-- Set up fixtures.
ALTER SEQUENCE users_user_id_seq RESTART 1;
INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year)
VALUES ('David', '1968-12-19', '12', '19', '1968'),
    ('Josh', '1970-03-12', '03', '12', '1970'),
    ('Dan', '1972-06-03', '6', '3', '1972'),
    ('Anna', '2005-06-03', '06', '3', '2005');

SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);

SELECT * FROM finish();
ROLLBACK;
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELECT can_ok(
   'find_by_birthday',
   ARRAY['integer', 'integer', 'integer', 'integer', 'text']
);

-- Set up fixtures.
ALTER SEQUENCE users_user_id_seq RESTART 1;
INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year)
VALUES ('David', '1968-12-19', '12', '19', '1968'),
    ('Josh', '1970-03-12', '03', '12', '1970'),
    ('Dan', '1972-06-03', '6', '3', '1972'),
    ('Anna', '2005-06-03', '06', '3', '2005');

SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);

SELECT * FROM finish();
ROLLBACK;
How We Doin’?
%
How We Doin’?
% pg_prove -d try test_schema.sql
test_find_by_bday.sql .. ok
All tests successful.
Files=1, Tests=3, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: PASS
%❚
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ),
   ARRAY[3,4],
   'Should fetch two birthdays for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one birthday for 3/6 OFFSET 1'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ),
   ARRAY[3],
   'Should fetch one birthday for 3/6 LIMIT 1'
);
UPDATE users SET state = 'inactive' WHERE user_id = 3;
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one active birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ),
   ARRAY[3],
   'Should fetch one inactive birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ),
   ARRAY[3,4],
   'Should fetch two birthdays for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one birthday for 3/6 OFFSET 1'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ),
   ARRAY[3],
   'Should fetch one birthday for 3/6 LIMIT 1'
);
UPDATE users SET state = 'inactive' WHERE user_id = 3;
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one active birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ),
   ARRAY[3],
   'Should fetch one inactive birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ),
   ARRAY[3,4],
   'Should fetch two birthdays for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one birthday for 3/6 OFFSET 1'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ),
   ARRAY[3],
   'Should fetch one birthday for 3/6 LIMIT 1'
);
UPDATE users SET state = 'inactive' WHERE user_id = 3;
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one active birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ),
   ARRAY[3],
   'Should fetch one inactive birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ),
   ARRAY[3,4],
   'Should fetch two birthdays for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one birthday for 3/6 OFFSET 1'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ),
   ARRAY[3],
   'Should fetch one birthday for 3/6 LIMIT 1'
);
UPDATE users SET state = 'inactive' WHERE user_id = 3;
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one active birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ),
   ARRAY[3],
   'Should fetch one inactive birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ),
   ARRAY[3,4],
   'Should fetch two birthdays for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one birthday for 3/6 OFFSET 1'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ),
   ARRAY[3],
   'Should fetch one birthday for 3/6 LIMIT 1'
);
UPDATE users SET state = 'inactive' WHERE user_id = 3;
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one active birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ),
   ARRAY[3],
   'Should fetch one inactive birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ),
   ARRAY[3,4],
   'Should fetch two birthdays for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one birthday for 3/6 OFFSET 1'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ),
   ARRAY[3],
   'Should fetch one birthday for 3/6 LIMIT 1'
);
UPDATE users SET state = 'inactive' WHERE user_id = 3;
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one active birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ),
   ARRAY[3],
   'Should fetch one inactive birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ),
   ARRAY[3,4],
   'Should fetch two birthdays for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one birthday for 3/6 OFFSET 1'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ),
   ARRAY[3],
   'Should fetch one birthday for 3/6 LIMIT 1'
);
UPDATE users SET state = 'inactive' WHERE user_id = 3;
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one active birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ),
   ARRAY[3],
   'Should fetch one inactive birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one birthday for 12/19'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ),
   ARRAY[3,4],
   'Should fetch two birthdays for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one birthday for 3/6 OFFSET 1'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ),
   ARRAY[3],
   'Should fetch one birthday for 3/6 LIMIT 1'
);
UPDATE users SET state = 'inactive' WHERE user_id = 3;
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ),
   ARRAY[4],
   'Should fetch one active birthday for 3/6'
);
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ),
   ARRAY[3],
   'Should fetch one inactive birthday for 3/6'
);
Still Good…
%
Still Good…
% pg_prove -d try test_schema.sql
test_find_by_bday.sql .. ok
All tests successful.
Files=1, Tests=8, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: PASS
%❚
NOW We Can
 Refactor
Let’s Go with SQL
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;
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;
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;
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;
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;
And That’s That
%
And That’s That
% pg_prove -d try test_schema.sql
test_find_by_bday.sql .. ok
All tests successful.
Files=1, Tests=8, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU)
Result: PASS
%❚
Hell Yes!
Let’s Review
Tests are for Finding Bugs
Tests are for Finding Bugs
  TDD not for finding bugs
Tests are for Finding Bugs
  TDD not for finding bugs

  TDD for sanity and consistency
Tests are for Finding Bugs
  TDD not for finding bugs

  TDD for sanity and consistency

  Tests prevent future bugs
Tests are Hard
Tests are Hard
Good frameworks easy
Tests are Hard
Good frameworks easy

pgTAP provides lots of assertions
Tests are Hard
Good frameworks easy

pgTAP provides lots of assertions

If you mean Hard to test interface:
Tests are Hard
Good frameworks easy

pgTAP provides lots of assertions

If you mean Hard to test interface:

  Red flag
Tests are Hard
Good frameworks easy

pgTAP provides lots of assertions

If you mean Hard to test interface:

  Red flag

  Think about refactoring
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…
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
Never Find Relevant Bugs
Never Find Relevant Bugs
 Tests don’t find bugs
Never Find Relevant Bugs
 Tests don’t find bugs

 Test PREVENT bugs
Never Find Relevant Bugs
 Tests don’t find bugs

 Test PREVENT bugs

 If your code doesn’t work…
Never Find Relevant Bugs
 Tests don’t find bugs

 Test PREVENT bugs

 If your code doesn’t work…

 That failure is RELEVANT, no?
Time-Consuming
Time-Consuming
Good frameworks easy to use
Time-Consuming
Good frameworks easy to use

Iterating between tests and code is natural
Time-Consuming
Good frameworks easy to use

Iterating between tests and code is natural

Tests are as fast as your code
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
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
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
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!
Running Tests is Slow
Running Tests is Slow
Test what you’re working on
Running Tests is Slow
Test what you’re working on

Set up automated testing for everything else
Running Tests is Slow
Test what you’re working on

Set up automated testing for everything else

Pay attention to automated test failures
For Inexperienced
    Developers
For Inexperienced
       Developers
I’ve been programming for 10 years
For Inexperienced
       Developers
I’ve been programming for 10 years

I have no idea what I was thinking a year ago
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
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
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
Unnecessary for
  Simple Code
Unnecessary for
       Simple Code
I copied fib() from a Perl library
Unnecessary for
       Simple Code
I copied fib() from a Perl library

It was dead simple
Unnecessary for
       Simple Code
I copied fib() from a Perl library

It was dead simple

And it was still wrong
Unnecessary for
       Simple Code
I copied fib() from a Perl library

It was dead simple

And it was still wrong

Tests keep even the simplest code working
Best for Fragile Code
Best for Fragile Code
All code is fragile
Best for Fragile Code
All code is fragile

Tests make code ROBUST
Best for Fragile Code
All code is fragile

Tests make code ROBUST

Add regression tests for bugs found by
Best for Fragile Code
All code is fragile

Tests make code ROBUST

Add regression tests for bugs found by

   Integration tests
Best for Fragile Code
All code is fragile

Tests make code ROBUST

Add regression tests for bugs found by

   Integration tests

   QA department
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
Users Test our Code
Users Test our Code
Talk about fragility
Users Test our Code
Talk about fragility

Staging servers never work
Users Test our Code
Talk about fragility

Staging servers never work

QA departments are disappearing
Users Test our Code
Talk about fragility

Staging servers never work

QA departments are disappearing

Users don’t want to see bugs
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 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
It’s a Private Function
It’s a Private Function
It still needs to work
It’s a Private Function
It still needs to work

It still needs to always work
It’s a Private Function
It still needs to work

It still needs to always work

Don’t reject glass box testing
It’s a Private Function
It still needs to work

It still needs to always work

Don’t reject glass box testing

Make sure that ALL interfaces work
Application Tests are
     Sufficient
Application Tests are
     Sufficient
App tests should connect as as app user
Application Tests are
     Sufficient
App tests should connect as as app user

May well be security limitations for the app
Application Tests are
     Sufficient
App tests should connect as as app user

May well be security limitations for the app

  Access only to functions
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
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 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
Tests Prove Nothing
Tests Prove Nothing
This is not a math equation
Tests Prove Nothing
This is not a math equation

This is about:
Tests Prove Nothing
This is not a math equation

This is about:

  consistency
Tests Prove Nothing
This is not a math equation

This is about:

  consistency

  stability
Tests Prove Nothing
This is not a math equation

This is about:

  consistency

  stability

  robusticity
Tests Prove Nothing
This is not a math equation

This is about:

   consistency

   stability

   robusticity

If a test fails, it has proved a failure
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
Tests are for Stable Code
Tests are for Stable Code
 How does it become stable?
Tests are for Stable Code
 How does it become stable?

 Tests the fastest route
Tests are for Stable Code
 How does it become stable?

 Tests the fastest route

 Ensure greater stability over time
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
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 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
I Really Like Detroit
I Really Like Detroit
I can’t help you
What’re You Waiting For?
What’re You Waiting For?
 pgTAP:   http:/
               /pgtap.projects.postgresql.org
What’re You Waiting For?
 pgTAP:    http:/
                /pgtap.projects.postgresql.org

 pgUnit:   http:/
                /en.dklab.ru/lib/dklab_pgunit
What’re You Waiting For?
 pgTAP:      http:/
                  /pgtap.projects.postgresql.org

 pgUnit:     http:/
                  /en.dklab.ru/lib/dklab_pgunit

 EpicTest:   http:/
                  /www.epictest.org
What’re You Waiting For?
 pgTAP:      http:/
                  /pgtap.projects.postgresql.org

 pgUnit:     http:/
                  /en.dklab.ru/lib/dklab_pgunit

 EpicTest:   http:/
                  /www.epictest.org

 pg_regress
Start
writing tests
Increase
consistency
Improve
stability
Save time
Free yourself
and…
Kick ass.
Thank You
Unit Test Your Database!
         David E. Wheeler
      PostgreSQL Experts, Inc.

      PGCon, May 21, 2009

Más contenido relacionado

La actualidad más candente

Boost Performance With My S Q L 51 Partitions
Boost Performance With  My S Q L 51 PartitionsBoost Performance With  My S Q L 51 Partitions
Boost Performance With My S Q L 51 PartitionsPerconaPerformance
 
Apache Cassandra Multi-Datacenter Essentials (Julien Anguenot, iLand Internet...
Apache Cassandra Multi-Datacenter Essentials (Julien Anguenot, iLand Internet...Apache Cassandra Multi-Datacenter Essentials (Julien Anguenot, iLand Internet...
Apache Cassandra Multi-Datacenter Essentials (Julien Anguenot, iLand Internet...DataStax
 
Percona Live 2012PPT: MySQL Query optimization
Percona Live 2012PPT: MySQL Query optimizationPercona Live 2012PPT: MySQL Query optimization
Percona Live 2012PPT: MySQL Query optimizationmysqlops
 
How to Analyze and Tune MySQL Queries for Better Performance
How to Analyze and Tune MySQL Queries for Better PerformanceHow to Analyze and Tune MySQL Queries for Better Performance
How to Analyze and Tune MySQL Queries for Better Performanceoysteing
 
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013Jaime Crespo
 
How to set up orchestrator to manage thousands of MySQL servers
How to set up orchestrator to manage thousands of MySQL serversHow to set up orchestrator to manage thousands of MySQL servers
How to set up orchestrator to manage thousands of MySQL serversSimon J Mudd
 
MySQL Indexing - Best practices for MySQL 5.6
MySQL Indexing - Best practices for MySQL 5.6MySQL Indexing - Best practices for MySQL 5.6
MySQL Indexing - Best practices for MySQL 5.6MYXPLAIN
 
Java concurrency in practice
Java concurrency in practiceJava concurrency in practice
Java concurrency in practiceMikalai Alimenkou
 
Redo log improvements MYSQL 8.0
Redo log improvements MYSQL 8.0Redo log improvements MYSQL 8.0
Redo log improvements MYSQL 8.0Mydbops
 
ioMemoryとAtomic Writeによるデータベース高速化
ioMemoryとAtomic Writeによるデータベース高速化ioMemoryとAtomic Writeによるデータベース高速化
ioMemoryとAtomic Writeによるデータベース高速化IIJ
 
MySQL Group Replication
MySQL Group ReplicationMySQL Group Replication
MySQL Group ReplicationKenny Gryp
 
ProxySQL - High Performance and HA Proxy for MySQL
ProxySQL - High Performance and HA Proxy for MySQLProxySQL - High Performance and HA Proxy for MySQL
ProxySQL - High Performance and HA Proxy for MySQLRené Cannaò
 
binary log と 2PC と Group Commit
binary log と 2PC と Group Commitbinary log と 2PC と Group Commit
binary log と 2PC と Group CommitTakanori Sejima
 

La actualidad más candente (20)

Boost Performance With My S Q L 51 Partitions
Boost Performance With  My S Q L 51 PartitionsBoost Performance With  My S Q L 51 Partitions
Boost Performance With My S Q L 51 Partitions
 
Apache Cassandra Multi-Datacenter Essentials (Julien Anguenot, iLand Internet...
Apache Cassandra Multi-Datacenter Essentials (Julien Anguenot, iLand Internet...Apache Cassandra Multi-Datacenter Essentials (Julien Anguenot, iLand Internet...
Apache Cassandra Multi-Datacenter Essentials (Julien Anguenot, iLand Internet...
 
Percona Live 2012PPT: MySQL Query optimization
Percona Live 2012PPT: MySQL Query optimizationPercona Live 2012PPT: MySQL Query optimization
Percona Live 2012PPT: MySQL Query optimization
 
How to Analyze and Tune MySQL Queries for Better Performance
How to Analyze and Tune MySQL Queries for Better PerformanceHow to Analyze and Tune MySQL Queries for Better Performance
How to Analyze and Tune MySQL Queries for Better Performance
 
UEFI HTTP/HTTPS Boot
UEFI HTTP/HTTPS BootUEFI HTTP/HTTPS Boot
UEFI HTTP/HTTPS Boot
 
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
 
PostgreSQL Replication Tutorial
PostgreSQL Replication TutorialPostgreSQL Replication Tutorial
PostgreSQL Replication Tutorial
 
How to set up orchestrator to manage thousands of MySQL servers
How to set up orchestrator to manage thousands of MySQL serversHow to set up orchestrator to manage thousands of MySQL servers
How to set up orchestrator to manage thousands of MySQL servers
 
An Overview on Nuxt.js
An Overview on Nuxt.jsAn Overview on Nuxt.js
An Overview on Nuxt.js
 
MySQL Shell for DBAs
MySQL Shell for DBAsMySQL Shell for DBAs
MySQL Shell for DBAs
 
MyRocks Deep Dive
MyRocks Deep DiveMyRocks Deep Dive
MyRocks Deep Dive
 
MySQL Indexing - Best practices for MySQL 5.6
MySQL Indexing - Best practices for MySQL 5.6MySQL Indexing - Best practices for MySQL 5.6
MySQL Indexing - Best practices for MySQL 5.6
 
Java concurrency in practice
Java concurrency in practiceJava concurrency in practice
Java concurrency in practice
 
Google V8 engine
Google V8 engineGoogle V8 engine
Google V8 engine
 
Explain that explain
Explain that explainExplain that explain
Explain that explain
 
Redo log improvements MYSQL 8.0
Redo log improvements MYSQL 8.0Redo log improvements MYSQL 8.0
Redo log improvements MYSQL 8.0
 
ioMemoryとAtomic Writeによるデータベース高速化
ioMemoryとAtomic Writeによるデータベース高速化ioMemoryとAtomic Writeによるデータベース高速化
ioMemoryとAtomic Writeによるデータベース高速化
 
MySQL Group Replication
MySQL Group ReplicationMySQL Group Replication
MySQL Group Replication
 
ProxySQL - High Performance and HA Proxy for MySQL
ProxySQL - High Performance and HA Proxy for MySQLProxySQL - High Performance and HA Proxy for MySQL
ProxySQL - High Performance and HA Proxy for MySQL
 
binary log と 2PC と Group Commit
binary log と 2PC と Group Commitbinary log と 2PC と Group Commit
binary log と 2PC と Group Commit
 

Similar a Unit Test Your Database

Practical unit testing 2014
Practical unit testing 2014Practical unit testing 2014
Practical unit testing 2014Andrew Fray
 
Das Frontend richtig Testen – mit Jest @Developer Week 2018
Das Frontend richtig Testen – mit Jest @Developer Week 2018Das Frontend richtig Testen – mit Jest @Developer Week 2018
Das Frontend richtig Testen – mit Jest @Developer Week 2018Holger Grosse-Plankermann
 
Jest: Frontend Testing leicht gemacht @EnterJS2018
Jest: Frontend Testing leicht gemacht @EnterJS2018Jest: Frontend Testing leicht gemacht @EnterJS2018
Jest: Frontend Testing leicht gemacht @EnterJS2018Holger Grosse-Plankermann
 
Developer Tests - Things to Know (Vilnius JUG)
Developer Tests - Things to Know (Vilnius JUG)Developer Tests - Things to Know (Vilnius JUG)
Developer Tests - Things to Know (Vilnius JUG)vilniusjug
 
Jest: Frontend Testing richtig gemacht @WebworkerNRW
Jest: Frontend Testing richtig gemacht @WebworkerNRWJest: Frontend Testing richtig gemacht @WebworkerNRW
Jest: Frontend Testing richtig gemacht @WebworkerNRWHolger Grosse-Plankermann
 
Software testing with examples in Angular (and AngularJS)
Software testing with examples in Angular (and AngularJS)Software testing with examples in Angular (and AngularJS)
Software testing with examples in Angular (and AngularJS)Paweł Żurowski
 
iOS Test-Driven Development
iOS Test-Driven DevelopmentiOS Test-Driven Development
iOS Test-Driven DevelopmentPablo Villar
 
Developer Tests - Things to Know
Developer Tests - Things to KnowDeveloper Tests - Things to Know
Developer Tests - Things to KnowVaidas Pilkauskas
 
DSR Testing (Part 1)
DSR Testing (Part 1)DSR Testing (Part 1)
DSR Testing (Part 1)Steve Upton
 
Test Driven Development with Laravel
Test Driven Development with LaravelTest Driven Development with Laravel
Test Driven Development with LaravelTyler Johnston
 
Unit tests & TDD
Unit tests & TDDUnit tests & TDD
Unit tests & TDDDror Helper
 
Presentations Unusual Java Bugs And Detecting Them Using Foss Tools
Presentations Unusual Java Bugs And Detecting Them Using Foss ToolsPresentations Unusual Java Bugs And Detecting Them Using Foss Tools
Presentations Unusual Java Bugs And Detecting Them Using Foss ToolsGanesh Samarthyam
 
Selenium
SeleniumSelenium
Seleniumg2ix
 
Test Driven Development Introduction
Test Driven Development IntroductionTest Driven Development Introduction
Test Driven Development IntroductionNguyen Hai
 
From 0 to 100: How we jump-started our frontend testing
From 0 to 100: How we jump-started our frontend testingFrom 0 to 100: How we jump-started our frontend testing
From 0 to 100: How we jump-started our frontend testingHenning Muszynski
 
Token Testing Slides
Token  Testing SlidesToken  Testing Slides
Token Testing Slidesericholscher
 

Similar a Unit Test Your Database (20)

Test
TestTest
Test
 
Practical unit testing 2014
Practical unit testing 2014Practical unit testing 2014
Practical unit testing 2014
 
Das Frontend richtig Testen – mit Jest @Developer Week 2018
Das Frontend richtig Testen – mit Jest @Developer Week 2018Das Frontend richtig Testen – mit Jest @Developer Week 2018
Das Frontend richtig Testen – mit Jest @Developer Week 2018
 
Jest: Frontend Testing leicht gemacht @EnterJS2018
Jest: Frontend Testing leicht gemacht @EnterJS2018Jest: Frontend Testing leicht gemacht @EnterJS2018
Jest: Frontend Testing leicht gemacht @EnterJS2018
 
Developer Tests - Things to Know (Vilnius JUG)
Developer Tests - Things to Know (Vilnius JUG)Developer Tests - Things to Know (Vilnius JUG)
Developer Tests - Things to Know (Vilnius JUG)
 
Jest: Frontend Testing richtig gemacht @WebworkerNRW
Jest: Frontend Testing richtig gemacht @WebworkerNRWJest: Frontend Testing richtig gemacht @WebworkerNRW
Jest: Frontend Testing richtig gemacht @WebworkerNRW
 
Software testing with examples in Angular (and AngularJS)
Software testing with examples in Angular (and AngularJS)Software testing with examples in Angular (and AngularJS)
Software testing with examples in Angular (and AngularJS)
 
iOS Test-Driven Development
iOS Test-Driven DevelopmentiOS Test-Driven Development
iOS Test-Driven Development
 
Developer Tests - Things to Know
Developer Tests - Things to KnowDeveloper Tests - Things to Know
Developer Tests - Things to Know
 
DSR Testing (Part 1)
DSR Testing (Part 1)DSR Testing (Part 1)
DSR Testing (Part 1)
 
Test Driven Development with Laravel
Test Driven Development with LaravelTest Driven Development with Laravel
Test Driven Development with Laravel
 
Unit tests & TDD
Unit tests & TDDUnit tests & TDD
Unit tests & TDD
 
Testing in Django
Testing in DjangoTesting in Django
Testing in Django
 
Presentations Unusual Java Bugs And Detecting Them Using Foss Tools
Presentations Unusual Java Bugs And Detecting Them Using Foss ToolsPresentations Unusual Java Bugs And Detecting Them Using Foss Tools
Presentations Unusual Java Bugs And Detecting Them Using Foss Tools
 
Selenium
SeleniumSelenium
Selenium
 
Test Driven Development Introduction
Test Driven Development IntroductionTest Driven Development Introduction
Test Driven Development Introduction
 
From 0 to 100: How we jump-started our frontend testing
From 0 to 100: How we jump-started our frontend testingFrom 0 to 100: How we jump-started our frontend testing
From 0 to 100: How we jump-started our frontend testing
 
Unit testing on mobile apps
Unit testing on mobile appsUnit testing on mobile apps
Unit testing on mobile apps
 
TDD and Getting Paid
TDD and Getting PaidTDD and Getting Paid
TDD and Getting Paid
 
Token Testing Slides
Token  Testing SlidesToken  Testing Slides
Token Testing Slides
 

Último

From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?Antenna Manufacturer Coco
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsJoaquim Jorge
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 

Último (20)

From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 

Unit Test Your Database

  • 1. Unit Test Your Database! David E. Wheeler PostgreSQL Experts, Inc. PGCon, May 21, 2009
  • 2.
  • 4.
  • 6.
  • 7. “It takes too long to write tests.”
  • 8.
  • 9. “Testing will just slow me down.”
  • 10.
  • 11. “It takes too long to run tests.”
  • 12.
  • 13. “We already write app-level unit tests.”
  • 14.
  • 15. “I test stuff by running my app.”
  • 16.
  • 17. “Tests never find relevant bugs.”
  • 18.
  • 19. “This code is so simple it doesn’t need tests.”
  • 20.
  • 21. “This function is too hard to test.”
  • 22.
  • 23. “This is a private function.”
  • 24.
  • 25. “Tests can't prove a program correct so why bother?”
  • 26.
  • 27. “The behavior of the code changes a lot and rewriting the tests to match will just slow things down.”
  • 28.
  • 29. “If I imagined a problem to write tests for, I probably wrote code that doesn’t have that problem.”
  • 30.
  • 31. “I can produce software that works even without focusing specifically on low- level unit tests.”
  • 32.
  • 33. “I’m lucky enough to only be dealing with really good developers.”
  • 34.
  • 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
  • 38. Test Conceptions For finding bugs Difficult
  • 39. Test Conceptions For finding bugs Difficult Irrelevant
  • 40. Test Conceptions For finding bugs Difficult Irrelevant Time-consuming
  • 41. Test Conceptions For finding bugs Difficult Irrelevant Time-consuming For inexperienced developers
  • 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
  • 50.
  • 52.
  • 53. What does it take?
  • 55. Test-Driven Development Say you need a Fibonacci Calculator
  • 56. Test-Driven Development Say you need a Fibonacci Calculator Start with a test
  • 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;
  • 70. Simple Function CREATE OR REPLACE FUNCTION fib ( fib_for integer ) RETURNS integer AS $$ BEGIN RETURN 0; END; $$ LANGUAGE plpgsql;
  • 71. Simple Function CREATE OR REPLACE FUNCTION fib ( fib_for integer ) RETURNS integer AS $$ BEGIN RETURN 0; END; $$ LANGUAGE plpgsql;
  • 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
  • 77. Add Assertions SELECT can('{fib}'); SELECT can_ok('fib', ARRAY['integer']);
  • 78. Add 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');
  • 79. Add 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');
  • 80. Add 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');
  • 81. %
  • 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');
  • 91. %
  • 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');
  • 100. %
  • 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;
  • 107. W00T! % psql -d try -f fib.sql CREATE FUNCTION % pg_prove -vd try test_fib.sql test_fib.sql .. ok All tests successful. Files=1, Tests=8, 0 secs (0.02 usr + 0.00 sys = 0.02 CPU) Result: PASS %❚
  • 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');
  • 111. We’re Golden! % psql -d try -f fib.sql CREATE FUNCTION % pg_prove -vd try test_fib.sql test_fib.sql .. ok All tests successful. Files=1, Tests=11, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU) Result: PASS %❚
  • 112.
  • 114.
  • 116.
  • 118.
  • 120.
  • 122. Detroit, we have a problem. try=#
  • 123. Detroit, we have a problem. try=# timing Timing is on. try=# select fib(30); fib -------- 832040 (1 row) Time: 6752.112 ms try=# ❚
  • 124. Detroit, we have a problem. try=# timing Timing is on. try=# select fib(30); fib -------- 832040 (1 row) Time: 6752.112 ms try=# ❚
  • 125.
  • 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 %❚
  • 133.
  • 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;
  • 135. %
  • 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;
  • 141. Back in Business % psql -d try -f fib.sql CREATE FUNCTION % pg_prove -vd try test_fib.sql test_fib.sql .. ok All tests successful. Files=1, Tests=12, 1 secs (0.02 usr + 0.01 sys = 0.03 CPU) Result: PASS %❚
  • 142.
  • 143.
  • 144. Just for the Hell of it…
  • 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');
  • 155. And Now? % psql -d try -f fib.sql CREATE FUNCTION % pg_prove -vd try test_fib.sql test_fib.sql .. ok All tests successful. Files=1, Tests=14, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU) Result: PASS %❚
  • 156.
  • 158.
  • 160.
  • 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;
  • 167. What’s on the Table? try=#
  • 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)
  • 170. What’s on the Table? try=#
  • 171. What’s on the Table? try=# select * from users; user_id | name | birthdate | birth_mon | birth_day | birth_year | state ---------+-------+------------+-----------+-----------+------------ +-------- 1 | David | 1968-12-19 | 12 | 19 | 1968 | active 2 | Josh | 1970-03-12 | 03 | 12 | 1970 | active 3 | Dan | 1972-06-03 | 6 |3 | 1972 | active (3 rows)
  • 172. What’s on the Table? try=# select * from users; user_id | name | birthdate | birth_mon | birth_day | birth_year | state ---------+-------+------------+-----------+-----------+------------ +-------- 1 | David | 1968-12-19 | 12 | 19 | 1968 | active 2 | Josh | 1970-03-12 | 03 | 12 | 1970 | active 3 | Dan | 1972-06-03 | 6 |3 | 1972 | active (3 rows)
  • 173.
  • 176. The Situation This is production code
  • 177. The Situation This is production code Cannot afford downtime
  • 178. The Situation This is production code Cannot afford downtime No room for mistakes
  • 179. The Situation This is production code Cannot afford downtime No room for mistakes Bugs must remain consistent
  • 180. The Situation This is production code Cannot afford downtime No room for mistakes Bugs must remain consistent But…
  • 181.
  • 182. Dear GOD it needs rewriting.
  • 183.
  • 185.
  • 187.
  • 188. BEGIN; SET search_path TO public, tap; SELECT plan(13); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'user_id' ); SELECT col_type_is( 'users', 'user_id', 'integer' ); SELECT col_is_pk( 'users', 'user_id' ); SELECT col_not_null( 'users', 'user_id' ); SELECT has_column( 'users', 'birthdate' ); SELECT col_type_is( 'users', 'birthdate', 'date' ); SELECT col_is_null( 'users', 'birthdate' ); SELECT has_column( 'users', 'state' ); SELECT col_type_is( 'users', 'state', 'text' ); SELECT col_not_null( 'users', 'state' ); SELECT col_default_is( 'users', 'state', 'active' ); SELECT * FROM finish(); ROLLBACK;
  • 189. BEGIN; SET search_path TO public, tap; SELECT plan(13); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'user_id' ); SELECT col_type_is( 'users', 'user_id', 'integer' ); SELECT col_is_pk( 'users', 'user_id' ); SELECT col_not_null( 'users', 'user_id' ); SELECT has_column( 'users', 'birthdate' ); SELECT col_type_is( 'users', 'birthdate', 'date' ); SELECT col_is_null( 'users', 'birthdate' ); SELECT has_column( 'users', 'state' ); SELECT col_type_is( 'users', 'state', 'text' ); SELECT col_not_null( 'users', 'state' ); SELECT col_default_is( 'users', 'state', 'active' ); SELECT * FROM finish(); ROLLBACK;
  • 190. BEGIN; SET search_path TO public, tap; SELECT plan(13); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'user_id' ); SELECT col_type_is( 'users', 'user_id', 'integer' ); SELECT col_is_pk( 'users', 'user_id' ); SELECT col_not_null( 'users', 'user_id' ); SELECT has_column( 'users', 'birthdate' ); SELECT col_type_is( 'users', 'birthdate', 'date' ); SELECT col_is_null( 'users', 'birthdate' ); SELECT has_column( 'users', 'state' ); SELECT col_type_is( 'users', 'state', 'text' ); SELECT col_not_null( 'users', 'state' ); SELECT col_default_is( 'users', 'state', 'active' ); SELECT * FROM finish(); ROLLBACK;
  • 191. BEGIN; SET search_path TO public, tap; SELECT plan(13); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'user_id' ); SELECT col_type_is( 'users', 'user_id', 'integer' ); SELECT col_is_pk( 'users', 'user_id' ); SELECT col_not_null( 'users', 'user_id' ); SELECT has_column( 'users', 'birthdate' ); SELECT col_type_is( 'users', 'birthdate', 'date' ); SELECT col_is_null( 'users', 'birthdate' ); SELECT has_column( 'users', 'state' ); SELECT col_type_is( 'users', 'state', 'text' ); SELECT col_not_null( 'users', 'state' ); SELECT col_default_is( 'users', 'state', 'active' ); SELECT * FROM finish(); ROLLBACK;
  • 192. BEGIN; SET search_path TO public, tap; SELECT plan(13); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'user_id' ); SELECT col_type_is( 'users', 'user_id', 'integer' ); SELECT col_is_pk( 'users', 'user_id' ); SELECT col_not_null( 'users', 'user_id' ); SELECT has_column( 'users', 'birthdate' ); SELECT col_type_is( 'users', 'birthdate', 'date' ); SELECT col_is_null( 'users', 'birthdate' ); SELECT has_column( 'users', 'state' ); SELECT col_type_is( 'users', 'state', 'text' ); SELECT col_not_null( 'users', 'state' ); SELECT col_default_is( 'users', 'state', 'active' ); SELECT * FROM finish(); ROLLBACK;
  • 194. Schema Sanity % pg_prove -d try test_schema.sql test_schema.sql .. ok All tests successful. Files=1, Tests=13, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU) Result: PASS %❚
  • 195.
  • 196. BEGIN; SET search_path TO public, tap; --SELECT plan(15); SELECT * FROM no_plan(); SELECT can('{find_by_birthday}'); SELECT can_ok( 'find_by_birthday', ARRAY['integer', 'integer', 'integer', 'integer', 'text'] ); -- Set up fixtures. ALTER SEQUENCE users_user_id_seq RESTART 1; INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year) VALUES ('David', '1968-12-19', '12', '19', '1968'), ('Josh', '1970-03-12', '03', '12', '1970'), ('Dan', '1972-06-03', '6', '3', '1972'), ('Anna', '2005-06-03', '06', '3', '2005'); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT * FROM finish(); ROLLBACK;
  • 197. BEGIN; SET search_path TO public, tap; --SELECT plan(15); SELECT * FROM no_plan(); SELECT can('{find_by_birthday}'); SELECT can_ok( 'find_by_birthday', ARRAY['integer', 'integer', 'integer', 'integer', 'text'] ); -- Set up fixtures. ALTER SEQUENCE users_user_id_seq RESTART 1; INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year) VALUES ('David', '1968-12-19', '12', '19', '1968'), ('Josh', '1970-03-12', '03', '12', '1970'), ('Dan', '1972-06-03', '6', '3', '1972'), ('Anna', '2005-06-03', '06', '3', '2005'); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT * FROM finish(); ROLLBACK;
  • 198. BEGIN; SET search_path TO public, tap; --SELECT plan(15); SELECT * FROM no_plan(); SELECT can('{find_by_birthday}'); SELECT can_ok( 'find_by_birthday', ARRAY['integer', 'integer', 'integer', 'integer', 'text'] ); -- Set up fixtures. ALTER SEQUENCE users_user_id_seq RESTART 1; INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year) VALUES ('David', '1968-12-19', '12', '19', '1968'), ('Josh', '1970-03-12', '03', '12', '1970'), ('Dan', '1972-06-03', '6', '3', '1972'), ('Anna', '2005-06-03', '06', '3', '2005'); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT * FROM finish(); ROLLBACK;
  • 199. BEGIN; SET search_path TO public, tap; --SELECT plan(15); SELECT * FROM no_plan(); SELECT can('{find_by_birthday}'); SELECT can_ok( 'find_by_birthday', ARRAY['integer', 'integer', 'integer', 'integer', 'text'] ); -- Set up fixtures. ALTER SEQUENCE users_user_id_seq RESTART 1; INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year) VALUES ('David', '1968-12-19', '12', '19', '1968'), ('Josh', '1970-03-12', '03', '12', '1970'), ('Dan', '1972-06-03', '6', '3', '1972'), ('Anna', '2005-06-03', '06', '3', '2005'); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT * FROM finish(); ROLLBACK;
  • 200. BEGIN; SET search_path TO public, tap; --SELECT plan(15); SELECT * FROM no_plan(); SELECT can('{find_by_birthday}'); SELECT can_ok( 'find_by_birthday', ARRAY['integer', 'integer', 'integer', 'integer', 'text'] ); -- Set up fixtures. ALTER SEQUENCE users_user_id_seq RESTART 1; INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year) VALUES ('David', '1968-12-19', '12', '19', '1968'), ('Josh', '1970-03-12', '03', '12', '1970'), ('Dan', '1972-06-03', '6', '3', '1972'), ('Anna', '2005-06-03', '06', '3', '2005'); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT * FROM finish(); ROLLBACK;
  • 201. BEGIN; SET search_path TO public, tap; --SELECT plan(15); SELECT * FROM no_plan(); SELECT can('{find_by_birthday}'); SELECT can_ok( 'find_by_birthday', ARRAY['integer', 'integer', 'integer', 'integer', 'text'] ); -- Set up fixtures. ALTER SEQUENCE users_user_id_seq RESTART 1; INSERT INTO users (name, birthdate, birth_mon, birth_day, birth_year) VALUES ('David', '1968-12-19', '12', '19', '1968'), ('Josh', '1970-03-12', '03', '12', '1970'), ('Dan', '1972-06-03', '6', '3', '1972'), ('Anna', '2005-06-03', '06', '3', '2005'); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT * FROM finish(); ROLLBACK;
  • 203. How We Doin’? % pg_prove -d try test_schema.sql test_find_by_bday.sql .. ok All tests successful. Files=1, Tests=3, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU) Result: PASS %❚
  • 204. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' );
  • 205. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ), ARRAY[3,4], 'Should fetch two birthdays for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ), ARRAY[4], 'Should fetch one birthday for 3/6 OFFSET 1' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ), ARRAY[3], 'Should fetch one birthday for 3/6 LIMIT 1' ); UPDATE users SET state = 'inactive' WHERE user_id = 3; SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ), ARRAY[4], 'Should fetch one active birthday for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ), ARRAY[3], 'Should fetch one inactive birthday for 3/6' );
  • 206. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ), ARRAY[3,4], 'Should fetch two birthdays for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ), ARRAY[4], 'Should fetch one birthday for 3/6 OFFSET 1' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ), ARRAY[3], 'Should fetch one birthday for 3/6 LIMIT 1' ); UPDATE users SET state = 'inactive' WHERE user_id = 3; SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ), ARRAY[4], 'Should fetch one active birthday for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ), ARRAY[3], 'Should fetch one inactive birthday for 3/6' );
  • 207. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ), ARRAY[3,4], 'Should fetch two birthdays for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ), ARRAY[4], 'Should fetch one birthday for 3/6 OFFSET 1' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ), ARRAY[3], 'Should fetch one birthday for 3/6 LIMIT 1' ); UPDATE users SET state = 'inactive' WHERE user_id = 3; SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ), ARRAY[4], 'Should fetch one active birthday for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ), ARRAY[3], 'Should fetch one inactive birthday for 3/6' );
  • 208. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ), ARRAY[3,4], 'Should fetch two birthdays for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ), ARRAY[4], 'Should fetch one birthday for 3/6 OFFSET 1' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ), ARRAY[3], 'Should fetch one birthday for 3/6 LIMIT 1' ); UPDATE users SET state = 'inactive' WHERE user_id = 3; SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ), ARRAY[4], 'Should fetch one active birthday for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ), ARRAY[3], 'Should fetch one inactive birthday for 3/6' );
  • 209. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ), ARRAY[3,4], 'Should fetch two birthdays for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ), ARRAY[4], 'Should fetch one birthday for 3/6 OFFSET 1' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ), ARRAY[3], 'Should fetch one birthday for 3/6 LIMIT 1' ); UPDATE users SET state = 'inactive' WHERE user_id = 3; SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ), ARRAY[4], 'Should fetch one active birthday for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ), ARRAY[3], 'Should fetch one inactive birthday for 3/6' );
  • 210. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ), ARRAY[3,4], 'Should fetch two birthdays for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ), ARRAY[4], 'Should fetch one birthday for 3/6 OFFSET 1' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ), ARRAY[3], 'Should fetch one birthday for 3/6 LIMIT 1' ); UPDATE users SET state = 'inactive' WHERE user_id = 3; SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ), ARRAY[4], 'Should fetch one active birthday for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ), ARRAY[3], 'Should fetch one inactive birthday for 3/6' );
  • 211. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ), ARRAY[3,4], 'Should fetch two birthdays for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ), ARRAY[4], 'Should fetch one birthday for 3/6 OFFSET 1' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ), ARRAY[3], 'Should fetch one birthday for 3/6 LIMIT 1' ); UPDATE users SET state = 'inactive' WHERE user_id = 3; SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ), ARRAY[4], 'Should fetch one active birthday for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ), ARRAY[3], 'Should fetch one inactive birthday for 3/6' );
  • 212. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'active' ) ), ARRAY[3,4], 'Should fetch two birthdays for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, 1, NULL, 'active' ) ), ARRAY[4], 'Should fetch one birthday for 3/6 OFFSET 1' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, 1, 'active' ) ), ARRAY[3], 'Should fetch one birthday for 3/6 LIMIT 1' ); UPDATE users SET state = 'inactive' WHERE user_id = 3; SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6 NULL, NULL, 'active' ) ), ARRAY[4], 'Should fetch one active birthday for 3/6' ); SELECT is( ARRAY( SELECT * FROM find_by_birthday( 3, 6, NULL, NULL, 'inactive' ) ), ARRAY[3], 'Should fetch one inactive birthday for 3/6' );
  • 214. Still Good… % pg_prove -d try test_schema.sql test_find_by_bday.sql .. ok All tests successful. Files=1, Tests=8, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU) Result: PASS %❚
  • 215.
  • 216. NOW We Can Refactor
  • 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;
  • 224. And That’s That % pg_prove -d try test_schema.sql test_find_by_bday.sql .. ok All tests successful. Files=1, Tests=8, 0 secs (0.02 usr + 0.01 sys = 0.03 CPU) Result: PASS %❚
  • 225.
  • 227.
  • 229. Tests are for Finding Bugs
  • 230. Tests are for Finding Bugs TDD not for finding bugs
  • 231. Tests are for Finding Bugs TDD not for finding bugs TDD for sanity and consistency
  • 232. Tests are for Finding Bugs TDD not for finding bugs TDD for sanity and consistency Tests prevent future bugs
  • 234. Tests are Hard Good frameworks easy
  • 235. Tests are Hard Good frameworks easy pgTAP provides lots of assertions
  • 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
  • 242. Never Find Relevant Bugs Tests don’t find bugs
  • 243. Never Find Relevant Bugs Tests don’t find bugs Test PREVENT bugs
  • 244. Never Find Relevant Bugs Tests don’t find bugs Test PREVENT bugs If your code doesn’t work…
  • 245. Never Find Relevant Bugs Tests don’t find bugs Test PREVENT bugs If your code doesn’t work… That failure is RELEVANT, no?
  • 248. Time-Consuming Good frameworks easy to use Iterating between tests and code is natural
  • 249. Time-Consuming Good frameworks easy to use Iterating between tests and code is natural Tests are as fast as your code
  • 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!
  • 255. Running Tests is Slow Test what you’re working on
  • 256. Running Tests is Slow Test what you’re working on Set up automated testing for everything else
  • 257. Running Tests is Slow Test what you’re working on Set up automated testing for everything else Pay attention to automated test failures
  • 258. For Inexperienced Developers
  • 259. For Inexperienced Developers I’ve been programming for 10 years
  • 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
  • 264. Unnecessary for Simple Code
  • 265. Unnecessary for Simple Code I copied fib() from a Perl library
  • 266. Unnecessary for Simple Code I copied fib() from a Perl library It was dead simple
  • 267. Unnecessary for Simple Code I copied fib() from a Perl library It was dead simple And it was still wrong
  • 268. Unnecessary for Simple Code I copied fib() from a Perl library It was dead simple And it was still wrong Tests keep even the simplest code working
  • 270. Best for Fragile Code All code is fragile
  • 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
  • 276. Users Test our Code
  • 277. Users Test our Code Talk about fragility
  • 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
  • 283. It’s a Private Function
  • 284. It’s a Private Function It still needs to work
  • 285. It’s a Private Function It still needs to work It still needs to always work
  • 286. It’s a Private Function It still needs to work It still needs to always work Don’t reject glass box testing
  • 287. It’s a Private Function It still needs to work It still needs to always work Don’t reject glass box testing Make sure that ALL interfaces work
  • 288. Application Tests are Sufficient
  • 289. Application Tests are Sufficient App tests should connect as as app user
  • 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
  • 296. Tests Prove Nothing This is not a math equation
  • 297. Tests Prove Nothing This is not a math equation This is about:
  • 298. Tests Prove Nothing This is not a math equation This is about: consistency
  • 299. Tests Prove Nothing This is not a math equation This is about: consistency stability
  • 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
  • 303. Tests are for Stable Code
  • 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
  • 310. I Really Like Detroit
  • 311. I Really Like Detroit I can’t help you
  • 313. What’re You Waiting For? pgTAP: http:/ /pgtap.projects.postgresql.org
  • 314. What’re You Waiting For? pgTAP: http:/ /pgtap.projects.postgresql.org pgUnit: http:/ /en.dklab.ru/lib/dklab_pgunit
  • 315. What’re You Waiting For? pgTAP: http:/ /pgtap.projects.postgresql.org pgUnit: http:/ /en.dklab.ru/lib/dklab_pgunit EpicTest: http:/ /www.epictest.org
  • 316. What’re You Waiting For? pgTAP: http:/ /pgtap.projects.postgresql.org pgUnit: http:/ /en.dklab.ru/lib/dklab_pgunit EpicTest: http:/ /www.epictest.org pg_regress
  • 317.
  • 319.
  • 321.
  • 323.
  • 325.
  • 327.
  • 328. and…
  • 329.
  • 331. Thank You Unit Test Your Database! David E. Wheeler PostgreSQL Experts, Inc. PGCon, May 21, 2009

Notas del editor

  1. The development priorities argument.
  2. The focus argument.
  3. The everything-by-hand argument
  4. The domain responsibility argument.
  5. The &#x201C;the app is the test&#x201D; argument.
  6. The relevancy argument.
  7. The &#x201C;it&#x2019;s just a one line change&#x201D; argument.
  8. The complexity argument.
  9. Which is a general rejection of the idea of glassbox testing, ie. that all tests must be regression tests.
  10. The Mathematician&#x2019;s argument.
  11. The flow and iteration argument
  12. The forsight argument.
  13. The &#x201C;it just works&#x201D; argument.
  14. The competence argument.
  15. Okay, you have me there.
  16. Okay, you have me there.
  17. Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  18. Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  19. Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  20. Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  21. Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  22. Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  23. Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  24. Rejection of glassbox testing, that is, that all tests must be regression tests.
  25. Rejection of glassbox testing, that is, that all tests must be regression tests.
  26. Rejection of glassbox testing, that is, that all tests must be regression tests.
  27. Rejection of glassbox testing, that is, that all tests must be regression tests.