File Coverage

blib/lib/Test/Sims.pm
Criterion Covered Total %
statement 94 95 98.9
branch 18 20 90.0
condition 3 3 100.0
subroutine 20 20 100.0
pod 2 2 100.0
total 137 140 97.8


line stmt bran cond sub pod time code
1             package Test::Sims;
2              
3 10     10   279640 use strict;
  10         59  
  10         245  
4 10     10   42 use warnings;
  10         14  
  10         521  
5              
6             our $VERSION = "20180103";
7              
8             =head1 NAME
9              
10             Test::Sims - Helps build semi-random data for testing
11              
12             =head1 SYNOPSIS
13              
14             package My::Sims;
15              
16             use Test::Sims;
17              
18             # Creates rand_name() and exported on demand.
19             make_rand name => [
20             qw(Mal Zoe Jayne Kaylee Inara River Simon Wash Zoe Book)
21             ];
22              
23             # Automatically exported
24             sub sim_character {
25             my %defaults = (
26             name => rand_name(),
27             series => "Firefly",
28             );
29              
30             require Character;
31             return Character->new(
32             %defaults, @_;
33             );
34             }
35              
36              
37             =head1 DESCRIPTION
38              
39             B! While very well tested behaviors may
40             change. The interface is not stable.
41              
42             This is a module to help building semi-random data for testing and to
43             create large, nested, interesting data structures.
44              
45             This module contains no new assertions, but it does tie in with
46             Test::Builder.
47              
48             It does two things. It contains functions which make generating
49             random data easier and it allows you to write repeatable, yet random,
50             test data.
51              
52             =head2 make_rand()
53              
54             my $code = make_rand $name => \@list;
55             my $code = make_rand $name => sub { ... };
56              
57             Creates a subroutine called C<> and exports it on request.
58              
59             If a @list is given it will generate a subroutine which returns
60             elements out of @list at random. It takes C and C arguments
61             to control how many.
62              
63             my @items = rand_$name(
64             min => $min_random_items,
65             max => $max_random_items
66             );
67              
68             C and C both default to 1. So by default you get 1 item.
69              
70             If a subroutine is given it will simply give that routine a name.
71             This is just to get the convenience of adding it to the exports.
72              
73             Also adds it to a "rand" export tag.
74              
75             {
76             package Sim::Firefly;
77              
78             make_rand crew => [
79             qw(Zoe Wash Mal River Simon Book Jayne Kaylee Inara)
80             ];
81             }
82              
83             ...later...
84              
85             {
86             use Sim::Firefly ":rand";
87              
88             my $crew = rand_crew; # 1 name
89             my @crew = rand_crew( max => 3 ); # 1, 2 or 3 names
90             }
91              
92              
93             =head2 export_sims()
94              
95             export_sims();
96              
97             A utility function which causes your module to export all the
98             functions called C<>. It also creates an export tag called
99             "sims".
100              
101             You should call this at the end of your Sim package.
102              
103              
104             =head2 Controlling randomness
105              
106             You can control the random seed used by Test::Sims by setting the
107             C environment variable. This is handy to make test runs
108             repeatable.
109              
110             TEST_SIMS_SEED=12345 perl -Ilib t/some_test.t
111              
112             Test::Sims will output the seed used at the end of each test run. If
113             the test failed it will be visible to the user, otherwise it will be a
114             TAP comment and only visible if the test is run verbosely.
115              
116             If having new data every run is too chaotic for you, you can set
117             TEST_SIMS_SEED to something which will remain fixed during a
118             development session. Perhaps the PID of your shell or your uid or
119             the date (20090704, for example).
120              
121              
122             =head2 C functions
123              
124             Test::Sims doesn't do anything with functions named C but
125             export them. Generally we recommend they're written like so:
126              
127             sub sim_thing {
128             my %defaults = (
129             name => rand_name(),
130             age => rand_age(),
131             motto => rand_text(),
132             picture => rand_image(),
133             );
134              
135             return Thing->new( %defaults, @_ );
136             }
137              
138             This way you can get a completely random Thing.
139              
140             my $thing = sim_thing();
141              
142             Or you can lock down the bits you need leaving the rest to float free.
143              
144             # Joe's motto and picture remain random
145             my $joe = sim_thing(
146             name => "Joe",
147             age => 64
148             );
149              
150              
151             =cut
152              
153 10     10   46 use base qw(Exporter);
  10         16  
  10         1653  
154             our @EXPORT = qw(make_rand export_sims);
155              
156             # Yes, its not a great seed but it doesn't have to be secure.
157             my $Seed = defined $ENV{TEST_SIMS_SEED} ? $ENV{TEST_SIMS_SEED} : (time ^ ($$ * $< * $());
158              
159             # XXX If something else calls srand() we're in trouble
160             srand $Seed;
161              
162             ## no critic (Subroutines::RequireArgUnpacking)
163             sub import {
164 7     7   158 my $class = shift;
165 7         14 my $caller = caller;
166              
167             {
168 10     10   63 no strict 'refs';
  10         40  
  10         2836  
  7         12  
169 7 100       66 unshift @{ $caller . "::ISA" }, "Exporter" unless $caller->isa("Exporter");
  6         58  
170             }
171              
172 7         6631 return __PACKAGE__->export_to_level( 1, $class, @_ );
173             }
174              
175             sub make_rand {
176 6     6 1 8868 my $name = shift;
177 6         8 my $thing = shift;
178              
179 6 100       46 my $items = ref $thing eq "ARRAY" ? $thing : [];
180              
181 6         14 my $caller = caller;
182              
183             my $code = ref $thing eq 'CODE' ? $thing : sub {
184 5     5   40 my %args = @_;
185 5 100       13 $args{min} = 1 unless defined $args{min};
186 5 100       11 $args{max} = 1 unless defined $args{max};
187              
188 5         14 my $max = int rand( $args{max} - $args{min} + 1 ) + $args{min};
189              
190 5         8 my @return;
191 5         8 for( 1 .. $max ) {
192 8         17 push @return, $items->[ rand @$items ];
193             }
194              
195 5 100       33 return @return == 1 ? $return[0] : @return;
196 6 100       31 };
197              
198 6         14 my $func = "rand_$name";
199 6         20 _alias( $caller, $func, $code );
200 6         15 _add_to_export_ok( $caller, $func );
201 6         14 _add_to_export_tags( $caller, $func, 'rand' );
202              
203 6         13 return $code;
204             }
205              
206             sub export_sims {
207 1     1 1 6 my $caller = caller;
208              
209 1         1 my $symbols = do {
210 10     10   70 no strict 'refs';
  10         23  
  10         1772  
211 1         2 \%{ $caller . '::' };
  1         2  
212             };
213              
214             my @sim_funcs = grep {
215 2         9 *{ $symbols->{$_} }{CODE}
216 2         2 }
217             grep {
218             # Protect against non-glob refs in the symbol table.
219 1         18 my $ref = ref $symbols->{$_};
  3         5  
220 3 50       12 $ref eq 'GLOB' || $ref eq ''
221             }
222             grep /^sim_/,
223             keys %$symbols;
224 1         3 for my $func (@sim_funcs) {
225 2         5 _add_to_export( $caller, $func );
226 2         3 _add_to_export_tags( $caller, $func, 'sims' );
227             }
228              
229 1         2 return;
230             }
231              
232             sub _add_to_export_ok {
233 6     6   12 my( $package, $func ) = @_;
234              
235 10     10   74 no strict 'refs';
  10         16  
  10         679  
236 6         10 push @{ $package . '::EXPORT_OK' }, $func;
  6         20  
237              
238 6         8 return;
239             }
240              
241             sub _add_to_export {
242 2     2   3 my( $package, $func ) = @_;
243              
244 10     10   58 no strict 'refs';
  10         16  
  10         636  
245 2         2 push @{ $package . '::EXPORT' }, $func;
  2         4  
246              
247 2         3 return;
248             }
249              
250             sub _add_to_export_tags {
251 8     8   18 my( $package, $func, $tag ) = @_;
252              
253 10     10   49 no strict 'refs';
  10         30  
  10         766  
254 8         10 my $export_tags = \%{ $package . '::EXPORT_TAGS' };
  8         22  
255 8         12 push @{ $export_tags->{$tag} }, $func;
  8         21  
256              
257 8         13 return;
258             }
259              
260             sub _alias {
261 6     6   15 my( $package, $func, $code ) = @_;
262              
263 10     10   61 no strict 'refs';
  10         28  
  10         2182  
264 6         8 *{ $package . '::' . $func } = $code;
  6         30  
265              
266 6         10 return;
267             }
268              
269              
270             sub _test_was_successful {
271 8     8   15 my $tb = shift;
272              
273 8 50       71 if( $tb->can("history") ) {
274 0         0 return $tb->history->test_was_successful;
275             }
276             else {
277 8   100     58 return $tb->summary && !( grep !$_, $tb->summary );
278             }
279             }
280              
281             sub _display_seed {
282 8     8   37 my $tb = shift;
283              
284 8         66 my $ok = _test_was_successful($tb);
285 8         1711 my $msg = "TEST_SIMS_SEED=$Seed";
286 8 100       49 $ok ? $tb->note($msg) : $tb->diag($msg);
287              
288 8         2148 return;
289             }
290              
291             END {
292 5     5   9610 require Test::Builder;
293 5         19 my $tb = Test::Builder->new;
294              
295 5 100       37 if( defined $tb->has_plan ) {
296 4         461 _display_seed($tb);
297             }
298             }
299              
300             1;
301              
302              
303             =head1 EXAMPLE
304              
305             Here's an example of making a simple package to generate random dates.
306              
307             package Sim::Date;
308              
309             use strict;
310             use warnings;
311              
312             require DateTime;
313             use Test::Sims;
314              
315             make_rand year => [1800..2100];
316              
317             sub sim_datetime {
318             my %args = @_;
319              
320             my $year = $args{year} || rand_year();
321             my $date = DateTime->new( year => $year );
322              
323             my $days_in_year = $date->is_leap_year ? 366 : 365;
324             my $secs = rand( $days_in_year * 24 * 60 * 60 );
325             $date->add( seconds => $secs );
326              
327             $date->set( %args );
328              
329             return $date;
330             }
331              
332             export_sims();
333              
334             And then using it.
335              
336             use Sim::Date;
337              
338             # Random date.
339             my $date = sim_datetime;
340              
341             # Random date in July 2009
342             my $date = sim_datetime(
343             year => 2009,
344             month => 7,
345             );
346              
347              
348             =head1 ENVIRONMENT
349              
350             =head3 TEST_SIMS_SEED
351              
352             If defined its value will be used to make tests repeatable. See
353             L.
354              
355              
356             =head1 SEE ALSO
357              
358             "Generating Test Data with The Sims"
359             L
360             is a set of slides outlining the Sims testing technique which this
361             module is supporting.
362              
363             L for common rand_* routines.
364              
365             L to generate random data from a set of rules.
366              
367              
368             =head1 SOURCE
369              
370             The source code repository can be found at
371             L.
372              
373             The latest release can be found at
374             L.
375              
376              
377             =head1 BUGS
378              
379             Please report bugs, problems, rough corners, feedback and suggestions
380             to L.
381              
382             Report early, report often.
383              
384              
385             =head1 THANKS
386              
387             Thanks go to the folks at Blackstar and Grant Street Group for helping
388             to develop this technique.
389              
390              
391             =head1 LICENSE and COPYRIGHT
392              
393             Copyright 2009 Michael G Schwern Eschwern@pobox.comE
394              
395             This program is free software; you can redistribute it and/or
396             modify it under the same terms as Perl itself.
397              
398             See F
399              
400             =cut
401