File Coverage

blib/lib/Class/DBI/Test/TempDB.pm
Criterion Covered Total %
statement 49 49 100.0
branch 11 18 61.1
condition 3 4 75.0
subroutine 11 11 100.0
pod 5 5 100.0
total 79 87 90.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             =head1 NAME
4              
5             Class::DBI::Test::TempDB - Maintain a SQLite database for testing CDBI
6              
7             =head1 Version
8              
9             Version 1.0
10              
11             =cut
12              
13             our $VERSION = '1.01';
14              
15             =head1 Synopsis
16              
17             package Music::TempDB;
18             use base qw/Class::DBI::Test::TempDB/;
19              
20             __PACKAGE__->build_test_db();
21              
22             END {
23             __PACKAGE__->tear_down_connection();
24             # remove the db file at unload time
25             }
26              
27             1;
28              
29             # ...Meanwhile, somewhere in Music::CD:
30              
31             =begin testing
32              
33             use Music::TempDB;
34             # have our class use the test db:
35             Music::TempDB->connect_class_to_test_db('Music::CD');
36              
37             # create some data in our test db:
38             my $cd = Music::CD->create({
39             title => "Jimmy Thudpucker's Greatest Hits",
40             year => 1978
41             });
42              
43             # make sure it looks right:
44             is($cd->year, 1978, 'year');
45             # etc.
46              
47             # ... when done testing, delete data:
48             $cd->delete();
49              
50             =end testing
51              
52              
53             =head1 Description
54              
55             In testing, we generally want tests to create and destroy all their own data.
56             When writing Class::DBI-based projects, it's helpful to have a test database in
57             which to do this, so that we can (a) be sure we're not stepping on production
58             data, and (b) be sure of exactly what data is in the test database at the
59             beginning/end of each test.
60              
61             Class::DBI::Test::TempDB handles the creation and destruction of a temporary
62             SQLite database on disk; it also allows you to point your Class::DBI classes at
63             the test database. You can then get on with creating, testing and destroying
64             test data simply by interacting with your Class::DBI classes.
65              
66             The database can be persistent between tests, or it can be recreated for each
67             test, from a YAML file describing the schema to be created. It can be stored in
68             a temp file, or in a file with a user-supplied name.
69              
70             Everything is done through class methods and subclassing.
71              
72             =cut
73              
74 1     1   145015 use strict;
  1         3  
  1         39  
75              
76             package Class::DBI::Test::TempDB;
77              
78 1     1   4 use base qw/Class::Data::Inheritable Class::DBI/;
  1         2  
  1         82  
79 1     1   5 use File::Temp qw/tempfile/;
  1         4  
  1         70  
80 1     1   851 use SQL::Translator;
  1         268390  
  1         31  
81 1     1   9 use Carp;
  1         3  
  1         630  
82              
83             __PACKAGE__->mk_classdata('dbfile');
84              
85             =begin testing
86              
87             use_ok('Class::DBI::Test::TempDB');
88              
89             =end testing
90              
91             =head1 Methods
92              
93             =head2 build_connection
94              
95             If supplied a parameter, that parameter is used as the name of a database file
96             to be built (or connected to, if it already exists). If not, creates a
97             temporary file (using File::Temp). Once the file is created, this method
98             initializes a SQLite db in it, and points our package connection at it.
99              
100             =cut
101              
102             sub build_connection {
103 3     3 1 1077 my ($class, $filename) = @_;
104 3         7 my $DB;
105 3 100       9 if ($filename) {
106 2         3 $DB = $filename;
107             } else {
108 1         6 (undef, $DB) = tempfile();
109             }
110              
111 3         617 $class->dbfile($DB);
112              
113 3         67 my @DSN = ($class->dsn, '', '', { AutoCommit => 1 });
114 3         59 $class->set_db(Main => @DSN);
115             }
116              
117             =head2 dsn
118              
119             Returns the string 'dbi:SQLite:dbname=' .
120              
121             =cut
122              
123             sub dsn {
124 5     5 1 1373 my $class = shift;
125 5         17 return "dbi:SQLite:dbname=" . $class->dbfile;
126             }
127              
128             =head2 tear_down_connection
129              
130             Removes the database file, if it still exists.
131              
132             =cut
133              
134             sub tear_down_connection {
135 3     3 1 132509 my $class = shift;
136 3 50       23 unlink $class->dbfile if (-e $class->dbfile);
137             }
138              
139             =head2 connect_class_to_test_db
140              
141             Overrides the db_Main() method of the passed-in CDBI entity class so that it
142             calls our db_Main() instead. This is the methodology suggested by the CDBI
143             documentation for dynamically changing a class's database connection. NB the
144             warnings there about already-existing instances of the entity class: that is,
145             this should probably only be done at the beginning of a test script, before any
146             objects have been instantiated in the entity class.
147              
148              
149             =cut
150              
151             sub connect_class_to_test_db {
152 1     1 1 20425 my ($class, $entityClass) = @_;
153              
154             # don't re-connect the class to our test db if the class is already
155             # using it:
156 1 50       14 unless ($class->db_Main == $entityClass->db_Main) {
157 1     2   3570 eval qq{
  2         3033  
158             sub ${entityClass}::db_Main {
159             return $class->db_Main;
160             }
161             };
162 1 50       180 die $@ if $@;
163             }
164             }
165              
166              
167             =head2 build_test_db
168              
169             MyClass::DBI::Test->build_test_db(<$yaml, $dbfile>);
170              
171             Given the path to a YAML file representing the schema of our production
172             database, generate a test SQLite database and use it.
173              
174             By default, $yaml is 'config.yaml' and $dbfile is a temp file (we use
175             File::Temp, which see for details).
176              
177             The YAML file is expected to be in the format produced by SQL::Translator
178             (which see for details); here's an example of a way to produce such a YAML file
179             from a MySQL database called 'mydatabase':
180              
181             mysqldump -d mydatabase | perl -MSQL::Translator -e
182             'my $sql = join "", ; my $trans = SQL::Translator->new;
183             print $trans->translate(from => "MySQL", to => "YAML", data => $sql);'
184             > config.yaml
185              
186             =begin testing
187              
188             ok(Class::DBI::Test::TempDB->build_test_db('t/files/config.yaml', 't/files/testdb.sqlite'),
189             'build test database');
190             ok(-e 't/files/testdb.sqlite', 'db file created');
191             ok(Class::DBI::Test::TempDB->tear_down_connection, 'tear_down_connection');
192             ok(!(-e 't/files/testdb.sqlite'), 'db file removed');
193              
194             =end testing
195              
196             =cut
197              
198             sub build_test_db {
199 2     2 1 4323 my ($class, $yaml, $filename) = @_;
200 2   50     9 $yaml ||= 'config.yaml';
201 2   100     11 $filename ||= '';
202 2         87 my $trans = SQL::Translator->new;
203 2 50       2107 my $sqlite_schema = $trans->translate(
204             from => "YAML", to => "SQLite", filename => $yaml) or
205             die $trans->error;
206              
207             # In order to execute the dumped sqlite schema statements against
208             # our dbh, we have to get rid of comments (lines starting with "--") and
209             # skip BEGIN TRANSACTION and COMMIT lines:
210              
211 2         122527 my @lines = split "\n", $sqlite_schema;
212 32         53 my @filtered_lines = grep {
213 2         7 $_ !~ /^\-\-/;
214             } @lines;
215              
216 2         10 $sqlite_schema = join "\n", @filtered_lines;
217              
218 2         8 my @statements = split /;/, $sqlite_schema;
219              
220 2 50       29 unlink $filename if (-e $filename);
221              
222 2         28 $class->build_connection($filename);
223              
224             # execute dumped sql schema against dbh:
225 2         412 foreach (@statements) {
226 6 50       15647 next if /^BEGIN\sTRANSACTION/;
227 6 100       27 next if /^\W*COMMIT/;
228 4 50       25 $class->db_Main->do($_) or warn $class->db_Main->errstr;
229             }
230              
231 2         8 return $class->db_Main;
232             }
233              
234             =begin testing
235              
236             use File::Temp;
237              
238             can_ok('Class::DBI::Test::TempDB', 'build_connection');
239             can_ok('Class::DBI::Test::TempDB', 'dsn');
240             can_ok('Class::DBI::Test::TempDB', 'connect_class_to_test_db');
241             can_ok('Class::DBI::Test::TempDB', 'tear_down_connection');
242              
243             package Car;
244              
245             use base 'Class::DBI';
246             Car->table('car');
247             Car->columns(All => qw/id make/);
248              
249             package Car::TestDBI;
250              
251             use base Class::DBI::Test::TempDB;
252              
253             package main;
254              
255             ok(Car::TestDBI->build_test_db('t/files/config.yaml'),
256             'build test database');
257              
258             my $dbh = Car::TestDBI->db_Main;
259              
260             $dbh->do(qq{
261             insert into car values (null, 'chevy')
262             }) or diag $dbh->errstr;
263              
264             my @DSN = (Car::TestDBI->dsn, '', '', { AutoCommit => 1 });
265             Car->set_db(Main => @DSN);
266              
267             my @cars = Car->retrieve_all;
268             my $car = $cars[0];
269             ok(eq_array([$car->id, $car->make], [1, 'chevy']), 'retrieve data from temp file');
270             ok($car->delete, 'delete CDBI object');
271              
272             Car::TestDBI->tear_down_connection;
273             ok (!(-e Car::TestDBI->dbfile), 'tear_down_connection(): temp file');
274              
275             Car::TestDBI->build_connection('/tmp/dbitestbase_test');
276             is(Car::TestDBI->dsn(), 'dbi:SQLite:dbname=/tmp/dbitestbase_test', 'dsn()');
277              
278             $dbh = Car::TestDBI->db_Main;
279             Car->clear_object_index;
280              
281             $dbh->do(qq{
282             create table car (
283             id integer primary key,
284             make varchar(255)
285             )
286             }) or diag $dbh->errstr;
287              
288             $dbh->do(qq{
289             insert into car values (null, 'nissan')
290             }) or diag $dbh->errstr;
291              
292             Car::TestDBI->connect_class_to_test_db('Car');
293              
294             @cars = Car->retrieve_all;
295             $car = $cars[0];
296             ok(eq_array([$car->id, $car->make], [1, 'nissan']), 'retrieve data from named file');
297              
298             Car::TestDBI->tear_down_connection;
299             ok (!(-e Car::TestDBI->dbfile), 'tear_down_connection(): named file');
300              
301             =end testing
302              
303             =head1 See Also
304              
305             L, L, L
306              
307             =head1 Limitations
308              
309             Of course, this module can only handle things that SQLite can handle.
310              
311             =head1 Author
312              
313             Dan Friedman, C<< >>
314              
315             =head1 Acknowledgements
316              
317             Thanks to Kirrily "Skud" Robert for early design input.
318              
319             Thanks to Tony Bowden for module naming help.
320              
321             Lots of ideas were taken from the testsuite that accompanies Class::DBI.
322              
323             =head1 Bugs
324              
325             Please report any bugs or feature requests to
326             C, or through the web interface at
327             L. I will be notified, and then you'll automatically
328             be notified of progress on your bug as I make changes.
329              
330             =head1 Copyright & License
331              
332             Copyright 2004 Dan Friedman, All Rights Reserved.
333              
334             This program is free software; you can redistribute it and/or modify it
335             under the same terms as Perl itself.
336              
337             =cut
338              
339             1; # End of Class::DBI::Test::TempDB