Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
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

11,892 views

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

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

×