Using Temporary Databases

     

Some programs rely on very specific database features. For example, a PostgreSQL or MySQL administration utility needs a deep knowledge of the underlying database. Other programs, including web content management systems, create their own tables and insert configuration data into the databases. Testing such systems with DBD::CSV is inappropriate; you won't cover enough of the system to be worthwhile.

In such cases, the best way to test your code is to test against a live database ”or, at least, a database containing actual data. If you're already creating database tables and rows with your installer, go a step further and create a test database with the same information.

How do I do that?

Assume that you have an application named My::App (saved as lib/My/App.pm ) and a file sql/schema.sql that holds your database schema and some basic data. You want to create both the live and test database tables during the installation process, and you need to know how to connect to the database to do so. One way to do this is to create a custom Module::Build subclass that asks the user for configuration information and installs the database along with the application.


Note: By storing this module in build_ lib/, the normal build process will not install it as it does modules in lib/ .

Save the following file to build_lib/MyBuild.pm :

 package MyBuild;     use base 'Module::Build';     use DBI;     use File::Path;     use Data::Dumper;     use File::Spec::Functions;     sub create_config_file     {         my $self     = shift;         my $config   =          {             db_type  => $self->prompt( 'Database type ',       'SQLite'   ),             user     => $self->prompt( 'Database user: ',      'root'     ),             password => $self->prompt( 'Database password: ',  's3kr1+'   ),             db_name  => $self->prompt( 'Database name: ',      'app_data' ),             test_db  => $self->prompt( 'Test database name: ', 'test_db'  ),         };         $self->notes( db_config    => $config );         mkpath( catdir( qw( lib My App ) ) );         my $dd       = Data::Dumper->new( [ $config ], [ 'db_config' ] );         my $path     = catfile(qw( lib My App Config.pm ));         open( my $file, '>', $path ) or die "Cannot write to '$path': $!\n";         printf $file <<'END_HERE', $dd->Dump(  );     package My::App::Config;     my $db_config;     %s     sub config     {         my ($self, $key) = @_;         return $db_config->{$key} if exists $db_config->{$key};     }     1;     END_HERE     }     sub create_database     {         my ($self, $dbname) = @_;         my $config          = $self->notes( 'db_config' );         my $dbpath          = catfile( 't', $dbname );         local $/            = ";\n";         local @ARGV         = catfile(qw( sql schema.sql ));         my @sql             = <>;         my $dbh             = DBI->connect(             "DBI:$config->{db_type}:dbname=$dbpath",             @$config{qw( user password )}         );         $dbh->do( $_ ) for @sql;       }     sub ACTION_build     {         my $self   = shift;         my $config = $self->notes( 'db_config' );         $self->create_database( $config->{db_name} );         $self->SUPER::ACTION_build( @_ );     }     sub ACTION_test     {         my $self   = shift;         my $config = $self->notes( 'db_config' );         $self->create_database( $config->{test_db} );         $self->SUPER::ACTION_test( @_ );     }     1; 

Save the following file to Build.PL :

 #!perl     use strict;     use warnings;     use lib 'build_lib';     use MyBuild;     my $build = MyBuild->new(         module_name    => 'My::App',         requires       =>         {             'DBI'         => '',             'DBD::SQLite' => '',         },         build_requires =>         {             'Test::Simple' => '',         },     );     $build->create_config_file(  );     $build->create_build_script(  ); 

Now run Build.PL :

 $  perl Build.PL  Database type  [SQLite]  SQLite  Database user:  [root]  root  Database password:  [s3kr1+]  s3kr1+  Database name:  [app_data]  app_data  Test database name:  [test_db]  test_db  Deleting Build     Removed previous script 'Build'     Creating new 'Build' script for 'My-App' version '1.00' 

Then build and test the module as usual:

 $  perl Build  Created database 'app_data'     lib/My/App/Config.pm -> blib/lib/My/App/Config.pm     lib/My/App.pm -> blib/lib/My/App.pm 

There aren't any tests yet, so save the following as t/myapp.t :

 #!perl     BEGIN     {         chdir 't' if -d 't';     }     use strict;     use warnings;     use Test::More 'no_plan'; # tests => 1;     use DBI;     use My::App::Config;     my $user    = My::App::Config->config( 'user'     );     my $pass    = My::App::Config->config( 'password' );     my $db_name = My::App::Config->config( 'test_db'  );     my $db_type = My::App::Config->config( 'db_type'  );     my $dbh     = DBI->connect( "DBI:$db_type:dbname=$db_name", $user, $pass );     my $module  = 'My::App';     use_ok( $module ) or exit; 


Note: SQLite databases don't really use usernames and passwords, but play along .

Run the (simple) test:

 $  perl Build test  Created database 'test_db'     t/myapp....ok                                                                     All tests successful.     Files=1, Tests=1,  0 wallclock secs ( 0.20 cusr +  0.00 csys =  0.20 CPU) 

What just happened ?

The initial build asked a few questions about the destination database before creating Build.PL . The MyBuild::create_config_file( ) method handles this, prompting for input while specifying sane defaults. If the user presses Enter or runs the program from an automated session such as a CPAN or a CPANPLUS shell, the program will accept the defaults.

More importantly, this also created a new file, lib/My/App/Config.pm . That's why running perl Build copied it into blib/ .

Both perl Build and perl Build test created databases, as seen in the Created database... output. This is the purpose of the MyBuild::ACTION_build( ) and MyBuild::ACTION_test( ) methods , which create the database with the appropriate name from the configuration data. The former builds the production database and the latter the testing database. If the user only runs perl Build , the program will not create the test database. It will create the test database only if the user runs the tests through perl Build test .


Note: How would you delete the test database after running the tests?

MyBuild::create_database( ) resembles the SQL handler seen earlier in Shipping Test Databases."

At the end of the program, the test file loads My::App::Config as a regular module and calls its config( ) method to retrieve information about the testing database. Then it creates a new DBI connection for that database, and it can run any tests that it wants.

What about...

Q:

What if the test runs somewhere without permission to create databases?

A:

That's a problem; the best you can do is to bail out early with a decent error message and suggestions to install things manually. You can run parts of your test suite if you haven't managed to create the test database; some tests are better than none.

Q:

Is it a good idea to use fake data in the test database?

A:

The further your test environment is from the live environment, the more difficult it is to have confidence that you've tested the right things. You may have genuine privacy or practicality concerns, especially if you have a huge dataset or if your test data includes confidential information. For the sake of speed and simplicity, consider testing a subset of the live data, but be sure to include edge cases and oddities that you expect to encounter.



Perl Testing. A Developer's Notebook
Perl Testing: A Developers Notebook
ISBN: 0596100922
EAN: 2147483647
Year: 2003
Pages: 107

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net