Your SlideShare is downloading. ×
0
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 test...
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 simp...
Test Conceptions
For finding bugs          Best for fragile code

Difficult

Irrelevant

Time-consuming

For inexperienced
d...
Test Conceptions
For finding bugs          Best for fragile code

Difficult                 Users test the code

Irrelevant
...
Test Conceptions
For finding bugs          Best for fragile code

Difficult                 Users test the code

Irrelevant ...
Test Conceptions
For finding bugs          Best for fragile code

Difficult                 Users test the code

Irrelevant ...
Test Conceptions
For finding bugs          Best for fragile code

Difficult                 Users test the code

Irrelevant ...
Test Conceptions
For finding bugs          Best for fragile code

Difficult                 Users test the code

Irrelevant ...
Test Conceptions
For finding bugs          Best for fragile code

Difficult                 Users test the code

Irrelevant ...
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

 ...
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

 ...
Simple Test
Simple Test

BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();

SELECT can('{fib}');
SELECT can_ok('fib', ARRA...
Simple Test

BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();

SELECT can('{fib}');
SELECT can_ok('fib', ARRA...
Simple Test

BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();

SELECT can('{fib}');
SELECT can_ok('fib', ARRA...
Simple Test

BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();

SELECT can('{fib}');
SELECT can_ok('fib', ARRA...
Simple Test

BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();

SELECT can('{fib}');
SELECT can_ok('fib', ARRA...
Simple Test

BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();

SELECT can('{fib}');
SELECT can_ok('fib', ARRA...
Simple Test

BEGIN;
SET search_path TO public, tap;
SELECT * FROM no_plan();

SELECT can('{fib}');
SELECT can_ok('fib', ARRA...
Simple Function
Simple Function

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

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
   RETURN 0;
END;
$$ LANG...
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...
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...
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...
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');...
Add Assertions
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should be 0');...
Add Assertions
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should be 0');...
%
% 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 o...
% 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 o...
Modify for the Test

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

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

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
   RETURN fib_for;
END...
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...
Add Another Assertion
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should ...
Add Another Assertion
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should ...
%
% 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 o...
% 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 o...
Modify to Pass

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
     RETURN fib_for;
END;
$...
Modify to Pass

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
   IF fib_for < 2 THEN
    ...
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 o...
Still More Assertions
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should ...
Still More Assertions
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should ...
%
% 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
# F...
% 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
# F...
% 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
# F...
Fix The Function

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
  IF fib_for < 2 THEN
   ...
Fix The Function

CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
  IF fib_for < 2 THEN
   ...
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...
A Few More Assertions
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should ...
A Few More Assertions
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should ...
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 successfu...
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 r...
Detroit, we have a
               problem.
try=#
    timing
Timing is on.
try=# select fib(30);

  fib
--------
 832040
(1 r...
Regression
Add a Regression Test
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should ...
Add a Regression Test
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should ...
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 - Sho...
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 - Sho...
Refactor
CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
DECLARE
   ret integer := 0;
   nxt intege...
%
% 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
# F...
% 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
# F...
CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
DECLARE
   ret integer := 0;
   nxt intege...
CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer AS $$
BEGIN
DECLARE
   ret integer := 0;
   nxt intege...
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 succes...
⌀
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');
SELEC...
Push It!
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should be 0');
SELEC...
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:...
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:...
CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS integer $$
                   AS
BEGIN
DECLARE
   ret     inte...
CREATE OR REPLACE FUNCTION fib (
   fib_for integer
) RETURNS bigint $$AS
BEGIN
DECLARE
   ret     bigint
             := 0;...
Apples to Apples…
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0, 'fib(0) should be 0...
Apples to Apples…
SELECT   can('{fib}');
SELECT   can_ok('fib', ARRAY['integer']);
SELECT   is( fib(0), 0::int8, 'fib(0) shoul...
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.
Fi...
TDD Means
Consistency
What about
Maintenance?
CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
  p_day ALIA...
CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
  p_day ALIA...
CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
  p_day ALIA...
CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
  p_day ALIA...
CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
  p_day ALIA...
CREATE FUNCTION find_by_birthday(integer, integer, integer, integer, text)
RETURNS SETOF integer AS $$
DECLARE
  p_day ALIA...
What’s on the Table?
try=#
What’s on the Table?
try=# users
    d
              Table quot;public.usersquot;
  Column |      Type       |     Modifier...
What’s on the Table?
try=# users
    d
              Table quot;public.usersquot;
  Column |      Type       |     Modifier...
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 | sta...
What’s on the Table?
try=#
    select * from users;
 user_id | name | birthdate | birth_mon | birth_day | birth_year | sta...
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   ...
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

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

SELECT   ...
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

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

SELECT   ...
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

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

SELECT   ...
BEGIN;
SET search_path TO public, tap;
SELECT plan(13);

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

SELECT   ...
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.0...
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELEC...
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELEC...
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELEC...
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELEC...
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELEC...
BEGIN;
SET search_path TO public, tap;
--SELECT plan(15);
SELECT * FROM no_plan();

SELECT can('{find_by_birthday}');
SELEC...
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 ...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
SELECT is(
   ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ),
   ARRAY[1],
   'Should fetch one bi...
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...
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,
   ...
Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
   p_day integer,
   p_mon integer,
   p_offset integer,
   ...
Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
   p_day integer,
   p_mon integer,
   p_offset integer,
   ...
Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
   p_day integer,
   p_mon integer,
   p_offset integer,
   ...
Let’s Go with SQL
CREATE OR REPLACE FUNCTION find_by_birthday(
   p_day integer,
   p_mon integer,
   p_offset integer,
   ...
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 sec...
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

  ...
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

  ...
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...
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

N...
Time-Consuming
Good frameworks easy to use

Iterating between tests and code is natural

Tests are as fast as your code

N...
Time-Consuming
Good frameworks easy to use

Iterating between tests and code is natural

Tests are as fast as your code

N...
Time-Consuming
Good frameworks easy to use

Iterating between tests and code is natural

Tests are as fast as your code

N...
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 automate...
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

Tes...
For Inexperienced
       Developers
I’ve been programming for 10 years

I have no idea what I was thinking a year ago

Tes...
For Inexperienced
       Developers
I’ve been programming for 10 years

I have no idea what I was thinking a year ago

Tes...
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 ke...
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 ...
Best for Fragile Code
All code is fragile

Tests make code ROBUST

Add regression tests for bugs found by

   Integration ...
Best for Fragile Code
All code is fragile

Tests make code ROBUST

Add regression tests for bugs found by

   Integration ...
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...
Users Test our Code
Talk about fragility

Staging servers never work

QA departments are disappearing

Users don’t want to...
Users Test our Code
Talk about fragility

Staging servers never work

QA departments are disappearing

Users don’t want to...
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 t...
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...
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...
Application Tests are
     Sufficient
App tests should connect as as app user

May well be security limitations for the app...
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 f...
Tests Prove Nothing
This is not a math equation

This is about:

   consistency

   stability

   robusticity

If a test f...
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...
Tests are for Stable Code
 How does it become stable?

 Tests the fastest route

 Ensure greater stability over time

 TDD...
Tests are for Stable Code
 How does it become stable?

 Tests the fastest route

 Ensure greater stability over time

 TDD...
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:/
             ...
What’re You Waiting For?
 pgTAP:      http:/
                  /pgtap.projects.postgresql.org

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

 pgUnit:     http:/
       ...
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
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Unit Test Your Database
Upcoming SlideShare
Loading in...5
×

Unit Test Your Database

10,560

Published on

Given that the database, as the canonical repository of data, is the most important part of many applications, why is it that we don't write database unit tests? This talk promotes the practice of implementing tests to directly test the schema, storage, and functionality of databases.

Published in: Technology, Business
3 Comments
20 Likes
Statistics
Notes
  • Hey Mr. Fish,

    You can grab a PDF of the presentation on the PGCon page for the talk.

    —Theory
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • I get the same unfortunately.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Dear Mr. Wheeler,

    I'm sorry, but I'm incredibly angry right now after trying in vein to view this presentation of yours. The online viewing takes a long time to download the slides (maybe due to Amazon's S3), but it is unusable. I tried to download the presentation file using 'Get File', but it is in Apple Keynote format and I don't see any way to view it on my Linux system.

    So I'm thinking all of this is incredibly lame, and I've given up on viewing this presentation.

    Regards,

    Shlomi Fish
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
10,560
On Slideshare
0
From Embeds
0
Number of Embeds
11
Actions
Shares
0
Downloads
498
Comments
3
Likes
20
Embeds 0
No embeds

No notes for slide



  • The development priorities argument.
  • The focus argument.
  • The everything-by-hand argument
  • The domain responsibility argument.
  • The &#x201C;the app is the test&#x201D; argument.
  • The relevancy argument.
  • The &#x201C;it&#x2019;s just a one line change&#x201D; argument.
  • The complexity argument.
  • Which is a general rejection of the idea of glassbox testing, ie. that all tests must be regression tests.
  • The Mathematician&#x2019;s argument.
  • The flow and iteration argument
  • The forsight argument.
  • The &#x201C;it just works&#x201D; argument.
  • The competence argument.
  • Okay, you have me there.
  • Okay, you have me there.
































































































































































































  • Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  • Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  • Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  • Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  • Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  • Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;
  • Red flag for &#x201C;this function does too much&#x201D; and &#x201C;this function is too tightly coupled.&#x201D;



































  • Rejection of glassbox testing, that is, that all tests must be regression tests.
  • Rejection of glassbox testing, that is, that all tests must be regression tests.
  • Rejection of glassbox testing, that is, that all tests must be regression tests.
  • Rejection of glassbox testing, that is, that all tests must be regression tests.
































  • Transcript of "Unit Test Your Database"

    1. 1. Unit Test Your Database! David E. Wheeler PostgreSQL Experts, Inc. PGCon, May 21, 2009
    2. 2. Why?
    3. 3. Do these sound familiar?
    4. 4. “It takes too long to write tests.”
    5. 5. “Testing will just slow me down.”
    6. 6. “It takes too long to run tests.”
    7. 7. “We already write app-level unit tests.”
    8. 8. “I test stuff by running my app.”
    9. 9. “Tests never find relevant bugs.”
    10. 10. “This code is so simple it doesn’t need tests.”
    11. 11. “This function is too hard to test.”
    12. 12. “This is a private function.”
    13. 13. “Tests can't prove a program correct so why bother?”
    14. 14. “The behavior of the code changes a lot and rewriting the tests to match will just slow things down.”
    15. 15. “If I imagined a problem to write tests for, I probably wrote code that doesn’t have that problem.”
    16. 16. “I can produce software that works even without focusing specifically on low- level unit tests.”
    17. 17. “I’m lucky enough to only be dealing with really good developers.”
    18. 18. “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
    19. 19. Test Conceptions
    20. 20. Test Conceptions For finding bugs
    21. 21. Test Conceptions For finding bugs Difficult
    22. 22. Test Conceptions For finding bugs Difficult Irrelevant
    23. 23. Test Conceptions For finding bugs Difficult Irrelevant Time-consuming
    24. 24. Test Conceptions For finding bugs Difficult Irrelevant Time-consuming For inexperienced developers
    25. 25. Test Conceptions For finding bugs Difficult Irrelevant Time-consuming For inexperienced developers Unnecessary for simple code
    26. 26. Test Conceptions For finding bugs Best for fragile code Difficult Irrelevant Time-consuming For inexperienced developers Unnecessary for simple code
    27. 27. Test Conceptions For finding bugs Best for fragile code Difficult Users test the code Irrelevant Time-consuming For inexperienced developers Unnecessary for simple code
    28. 28. 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
    29. 29. 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
    30. 30. 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
    31. 31. 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
    32. 32. 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
    33. 33. Let’s Get Real
    34. 34. What does it take?
    35. 35. Test-Driven Development
    36. 36. Test-Driven Development Say you need a Fibonacci Calculator
    37. 37. Test-Driven Development Say you need a Fibonacci Calculator Start with a test
    38. 38. Test-Driven Development Say you need a Fibonacci Calculator Start with a test Write the simplest possible function
    39. 39. Test-Driven Development Say you need a Fibonacci Calculator Start with a test Write the simplest possible function Add more tests
    40. 40. Test-Driven Development Say you need a Fibonacci Calculator Start with a test Write the simplest possible function Add more tests Update the function
    41. 41. 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…
    42. 42. Simple Test
    43. 43. 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;
    44. 44. 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;
    45. 45. 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;
    46. 46. 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;
    47. 47. 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;
    48. 48. 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;
    49. 49. 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;
    50. 50. Simple Function
    51. 51. Simple Function CREATE OR REPLACE FUNCTION fib ( fib_for integer ) RETURNS integer AS $$ BEGIN RETURN 0; END; $$ LANGUAGE plpgsql;
    52. 52. Simple Function CREATE OR REPLACE FUNCTION fib ( fib_for integer ) RETURNS integer AS $$ BEGIN RETURN 0; END; $$ LANGUAGE plpgsql;
    53. 53. Run the Test %
    54. 54. Run the Test % psql -d try -f fib.sql CREATE FUNCTION % ❚
    55. 55. 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 %❚
    56. 56. 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 %❚
    57. 57. 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
    58. 58. Add Assertions SELECT can('{fib}'); SELECT can_ok('fib', ARRAY['integer']);
    59. 59. 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');
    60. 60. 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');
    61. 61. 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');
    62. 62. %
    63. 63. % 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 %❚
    64. 64. % 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 %❚
    65. 65. Modify for the Test CREATE OR REPLACE FUNCTION fib ( fib_for integer ) RETURNS integer AS $$ BEGIN RETURN 0; END; $$ LANGUAGE plpgsql;
    66. 66. Modify for the Test CREATE OR REPLACE FUNCTION fib ( fib_for integer ) RETURNS integer AS $$ BEGIN RETURN fib_for; END; $$ LANGUAGE plpgsql;
    67. 67. Modify for the Test CREATE OR REPLACE FUNCTION fib ( fib_for integer ) RETURNS integer AS $$ BEGIN RETURN fib_for; END; $$ LANGUAGE plpgsql; Bare minimum
    68. 68. Tests Pass! %
    69. 69. 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 %❚
    70. 70. 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');
    71. 71. 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');
    72. 72. %
    73. 73. % 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 %❚
    74. 74. % 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 %❚
    75. 75. Modify to Pass CREATE OR REPLACE FUNCTION fib ( fib_for integer ) RETURNS integer AS $$ BEGIN RETURN fib_for; END; $$ LANGUAGE plpgsql;
    76. 76. 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;
    77. 77. And…Pass! %
    78. 78. 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 %❚
    79. 79. 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');
    80. 80. 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');
    81. 81. %
    82. 82. % 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 %❚
    83. 83. % 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 %❚
    84. 84. % 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 %❚
    85. 85. 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;
    86. 86. 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;
    87. 87. W00T! %
    88. 88. 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 %❚
    89. 89. 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');
    90. 90. 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');
    91. 91. We’re Golden! %
    92. 92. 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 %❚
    93. 93. Make it so, Number One.
    94. 94. OMG WTF???
    95. 95. The server is hammered!
    96. 96. Debug, debug, debug…
    97. 97. Nailed it!
    98. 98. Detroit, we have a problem. try=#
    99. 99. Detroit, we have a problem. try=# timing Timing is on. try=# select fib(30); fib -------- 832040 (1 row) Time: 6752.112 ms try=# ❚
    100. 100. Detroit, we have a problem. try=# timing Timing is on. try=# select fib(30); fib -------- 832040 (1 row) Time: 6752.112 ms try=# ❚
    101. 101. Regression
    102. 102. 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');
    103. 103. 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 );
    104. 104. What’ve We Got? %
    105. 105. 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 %❚
    106. 106. 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 %❚
    107. 107. Refactor
    108. 108. 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;
    109. 109. %
    110. 110. % 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
    111. 111. % 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
    112. 112. 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;
    113. 113. 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;
    114. 114. Back in Business %
    115. 115. 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 %❚
    116. 116.
    117. 117. Just for the Hell of it…
    118. 118. 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 );
    119. 119. 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');
    120. 120. No Fibbing. %
    121. 121. 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 %❚
    122. 122. 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 %❚
    123. 123. 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;
    124. 124. 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;
    125. 125. 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');
    126. 126. 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');
    127. 127. And Now? %
    128. 128. 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 %❚
    129. 129. TDD Means Consistency
    130. 130. What about Maintenance?
    131. 131. 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;
    132. 132. 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;
    133. 133. 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;
    134. 134. 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;
    135. 135. 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;
    136. 136. 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;
    137. 137. What’s on the Table? try=#
    138. 138. 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)
    139. 139. 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)
    140. 140. What’s on the Table? try=#
    141. 141. 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)
    142. 142. 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)
    143. 143. Must… restrain… fist… of death…
    144. 144. The Situation
    145. 145. The Situation This is production code
    146. 146. The Situation This is production code Cannot afford downtime
    147. 147. The Situation This is production code Cannot afford downtime No room for mistakes
    148. 148. The Situation This is production code Cannot afford downtime No room for mistakes Bugs must remain consistent
    149. 149. The Situation This is production code Cannot afford downtime No room for mistakes Bugs must remain consistent But…
    150. 150. Dear GOD it needs rewriting.
    151. 151. But first…
    152. 152. Test the existing implementation.
    153. 153. 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;
    154. 154. 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;
    155. 155. 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;
    156. 156. 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;
    157. 157. 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;
    158. 158. Schema Sanity %
    159. 159. 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 %❚
    160. 160. 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;
    161. 161. 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;
    162. 162. 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;
    163. 163. 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;
    164. 164. 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;
    165. 165. 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;
    166. 166. How We Doin’? %
    167. 167. 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 %❚
    168. 168. SELECT is( ARRAY( SELECT * FROM find_by_birthday( 19, 12, NULL, NULL, 'active' ) ), ARRAY[1], 'Should fetch one birthday for 12/19' );
    169. 169. 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' );
    170. 170. 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' );
    171. 171. 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' );
    172. 172. 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' );
    173. 173. 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' );
    174. 174. 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' );
    175. 175. 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' );
    176. 176. 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' );
    177. 177. Still Good… %
    178. 178. 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 %❚
    179. 179. NOW We Can Refactor
    180. 180. Let’s Go with SQL
    181. 181. 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;
    182. 182. 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;
    183. 183. 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;
    184. 184. 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;
    185. 185. 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;
    186. 186. And That’s That %
    187. 187. 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 %❚
    188. 188. Hell Yes!
    189. 189. Let’s Review
    190. 190. Tests are for Finding Bugs
    191. 191. Tests are for Finding Bugs TDD not for finding bugs
    192. 192. Tests are for Finding Bugs TDD not for finding bugs TDD for sanity and consistency
    193. 193. Tests are for Finding Bugs TDD not for finding bugs TDD for sanity and consistency Tests prevent future bugs
    194. 194. Tests are Hard
    195. 195. Tests are Hard Good frameworks easy
    196. 196. Tests are Hard Good frameworks easy pgTAP provides lots of assertions
    197. 197. Tests are Hard Good frameworks easy pgTAP provides lots of assertions If you mean Hard to test interface:
    198. 198. Tests are Hard Good frameworks easy pgTAP provides lots of assertions If you mean Hard to test interface: Red flag
    199. 199. Tests are Hard Good frameworks easy pgTAP provides lots of assertions If you mean Hard to test interface: Red flag Think about refactoring
    200. 200. 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…
    201. 201. 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
    202. 202. Never Find Relevant Bugs
    203. 203. Never Find Relevant Bugs Tests don’t find bugs
    204. 204. Never Find Relevant Bugs Tests don’t find bugs Test PREVENT bugs
    205. 205. Never Find Relevant Bugs Tests don’t find bugs Test PREVENT bugs If your code doesn’t work…
    206. 206. Never Find Relevant Bugs Tests don’t find bugs Test PREVENT bugs If your code doesn’t work… That failure is RELEVANT, no?
    207. 207. Time-Consuming
    208. 208. Time-Consuming Good frameworks easy to use
    209. 209. Time-Consuming Good frameworks easy to use Iterating between tests and code is natural
    210. 210. Time-Consuming Good frameworks easy to use Iterating between tests and code is natural Tests are as fast as your code
    211. 211. 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
    212. 212. 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
    213. 213. 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
    214. 214. 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!
    215. 215. Running Tests is Slow
    216. 216. Running Tests is Slow Test what you’re working on
    217. 217. Running Tests is Slow Test what you’re working on Set up automated testing for everything else
    218. 218. Running Tests is Slow Test what you’re working on Set up automated testing for everything else Pay attention to automated test failures
    219. 219. For Inexperienced Developers
    220. 220. For Inexperienced Developers I’ve been programming for 10 years
    221. 221. For Inexperienced Developers I’ve been programming for 10 years I have no idea what I was thinking a year ago
    222. 222. 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
    223. 223. 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
    224. 224. 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
    225. 225. Unnecessary for Simple Code
    226. 226. Unnecessary for Simple Code I copied fib() from a Perl library
    227. 227. Unnecessary for Simple Code I copied fib() from a Perl library It was dead simple
    228. 228. Unnecessary for Simple Code I copied fib() from a Perl library It was dead simple And it was still wrong
    229. 229. 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
    230. 230. Best for Fragile Code
    231. 231. Best for Fragile Code All code is fragile
    232. 232. Best for Fragile Code All code is fragile Tests make code ROBUST
    233. 233. Best for Fragile Code All code is fragile Tests make code ROBUST Add regression tests for bugs found by
    234. 234. Best for Fragile Code All code is fragile Tests make code ROBUST Add regression tests for bugs found by Integration tests
    235. 235. Best for Fragile Code All code is fragile Tests make code ROBUST Add regression tests for bugs found by Integration tests QA department
    236. 236. 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
    237. 237. Users Test our Code
    238. 238. Users Test our Code Talk about fragility
    239. 239. Users Test our Code Talk about fragility Staging servers never work
    240. 240. Users Test our Code Talk about fragility Staging servers never work QA departments are disappearing
    241. 241. Users Test our Code Talk about fragility Staging servers never work QA departments are disappearing Users don’t want to see bugs
    242. 242. 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
    243. 243. 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
    244. 244. It’s a Private Function
    245. 245. It’s a Private Function It still needs to work
    246. 246. It’s a Private Function It still needs to work It still needs to always work
    247. 247. It’s a Private Function It still needs to work It still needs to always work Don’t reject glass box testing
    248. 248. 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
    249. 249. Application Tests are Sufficient
    250. 250. Application Tests are Sufficient App tests should connect as as app user
    251. 251. Application Tests are Sufficient App tests should connect as as app user May well be security limitations for the app
    252. 252. Application Tests are Sufficient App tests should connect as as app user May well be security limitations for the app Access only to functions
    253. 253. 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
    254. 254. 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
    255. 255. 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
    256. 256. Tests Prove Nothing
    257. 257. Tests Prove Nothing This is not a math equation
    258. 258. Tests Prove Nothing This is not a math equation This is about:
    259. 259. Tests Prove Nothing This is not a math equation This is about: consistency
    260. 260. Tests Prove Nothing This is not a math equation This is about: consistency stability
    261. 261. Tests Prove Nothing This is not a math equation This is about: consistency stability robusticity
    262. 262. Tests Prove Nothing This is not a math equation This is about: consistency stability robusticity If a test fails, it has proved a failure
    263. 263. 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
    264. 264. Tests are for Stable Code
    265. 265. Tests are for Stable Code How does it become stable?
    266. 266. Tests are for Stable Code How does it become stable? Tests the fastest route
    267. 267. Tests are for Stable Code How does it become stable? Tests the fastest route Ensure greater stability over time
    268. 268. 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
    269. 269. 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
    270. 270. 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
    271. 271. I Really Like Detroit
    272. 272. I Really Like Detroit I can’t help you
    273. 273. What’re You Waiting For?
    274. 274. What’re You Waiting For? pgTAP: http:/ /pgtap.projects.postgresql.org
    275. 275. What’re You Waiting For? pgTAP: http:/ /pgtap.projects.postgresql.org pgUnit: http:/ /en.dklab.ru/lib/dklab_pgunit
    276. 276. What’re You Waiting For? pgTAP: http:/ /pgtap.projects.postgresql.org pgUnit: http:/ /en.dklab.ru/lib/dklab_pgunit EpicTest: http:/ /www.epictest.org
    277. 277. 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
    278. 278. Start writing tests
    279. 279. Increase consistency
    280. 280. Improve stability
    281. 281. Save time
    282. 282. Free yourself
    283. 283. and…
    284. 284. Kick ass.
    285. 285. Thank You Unit Test Your Database! David E. Wheeler PostgreSQL Experts, Inc. PGCon, May 21, 2009
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×