File Coverage

blib/lib/Test/Skeleton/Generator.pm
Criterion Covered Total %
statement 113 130 86.9
branch 28 34 82.3
condition 5 6 83.3
subroutine 21 23 91.3
pod 0 9 0.0
total 167 202 82.6


line stmt bran cond sub pod time code
1             package Test::Skeleton::Generator;
2              
3 1     1   75868 use 5.001000;
  1         4  
  1         36  
4 1     1   5 use strict;
  1         1  
  1         28  
5 1     1   4 use warnings;
  1         6  
  1         30  
6 1     1   955 use autodie;
  1         18914  
  1         8  
7              
8             our $VERSION = "0.06";
9              
10 1     1   10046 use HTML::Template;
  1         22717  
  1         47  
11 1     1   984 use Class::Inspector;
  1         4416  
  1         38  
12 1     1   927 use Sub::Information;
  1         2022  
  1         6  
13 1     1   60 use File::Basename;
  1         2  
  1         89  
14 1     1   7 use File::Path qw/ make_path /;
  1         2  
  1         1896  
15              
16             my $debug = 0;
17              
18             =head1 NAME
19              
20             Test::Skeleton::Generator - Create a skeleton for a test file based on an existing module
21              
22             =head1 SYNOPSIS
23              
24             simply
25              
26             generate_perl_test_skeleton -p ./lib/Module.pm -t t/test.t
27              
28             Or from another script/module:
29              
30             use Test::Skeleton::Generator;
31             my $generator = Test::Skeleton::Generator->new( {
32             package_file => './lib/Some/Module.pm',
33             skip_private_methods => 1,
34             } );
35             my $test_file_content = $generator->get_test;
36              
37             =head1 DESCRIPTION
38              
39             Test::Skeleton::Generator is supposed to be used from within your editor to quickly
40             generate skeletons/stubs for a test file that is will test the module you are
41             currently working on.
42              
43             So suppose you are working on the file C<./lib/Foo/Bar.pm> which hasn't got any tests
44             yet. Now you simply press a keyboard shortcut or click an icon (if you really have to)
45             and your editor will simply call perl like in the SYNOPSIS above to generate a .t file
46             in your ./t/ directory. You don't have to write the boiler-plate code yourself.
47              
48             Basically, there are two ways to use this module:
49              
50             The simplest way to use this module is from the command line. Simply use the distributed
51             script C. See its documentation for more details.
52              
53             But if you find it useful, you can also use it from another script or module which
54             will give you more option and lets you handle the content of the future test file yourself.
55              
56             =head2 Generated Code
57              
58             The code generated by this module, basically looks like this:
59              
60             use Test::Most;
61              
62             BEGIN {
63             use_ok( 'Your::Module' );
64             }
65              
66             test_sub1();
67             test_sub2();
68              
69             done_testing;
70              
71             sub get_object {
72             return Your::Module->new( @_ );
73             }
74              
75             sub test_sub1 {
76             can_ok 'Your::Module', 'sub1';
77             }
78              
79             sub test_sub2 {
80             can_ok 'Your::Module', 'sub2';
81             }
82              
83             So for each subroutine in your original module, you get a subroutine in the generated
84             test code that is supposed to test the original sub's code. You also get a function
85             that I find very handy, that you can call to get an object instance of your package.
86             I should probably add an option to Test::Skeletion::Generator to suppress the generation
87             of that sub. But you can also simply delete it, of course.
88              
89             =head1 SEE ALSO
90              
91             L is a module that is very similar to this one. It uses L
92             to analyze a given module and it will then generate a test stub. It even has an
93             option to run L on the generated test code. The only thing I don't
94             like about it is that it won't generate a test subroutine for each subroutine in
95             the module.
96              
97             =head1 LICENSE
98              
99             Copyright (C) Manni Heumann.
100              
101             This library is free software; you can redistribute it and/or modify
102             it under the same terms as Perl itself.
103              
104             =head1 AUTHOR
105              
106             Manni Heumann Egithub@lxxi.orgE
107              
108             =cut
109              
110              
111             sub new {
112 12     12 0 22871 my $class = shift;
113 12         17 my $options = shift;
114              
115 12         58 my $self = bless {
116             package_file => '',
117             test_file => '',
118             skip_private_methods => 0,
119             debug => 0,
120             }, $class;
121              
122 12 100       35 if ( $options ) {
123 11         39 foreach my $key ( keys %$self ) {
124 44 100       101 $self->{ $key } = $options->{ $key } if exists $options->{ $key };
125             };
126 11 100       726 $debug = 1 if $self->{ debug };
127 11         32 return $self;
128             }
129             else {
130 1         14 die 'You need to provide the name of the package and the path to the test file.';
131             }
132             }
133              
134             sub get_test {
135 0     0 0 0 my $self = shift;
136              
137 0         0 my $package = $self->get_package_name;
138 0         0 my $existing_test_subs = $self->analyze_t_file;
139 0         0 my $subs_needing_tests = $self->get_package_functions( $package, $existing_test_subs );
140              
141 0 0       0 $self->prepare_test_file_path unless -e $self->{ test_file };
142              
143 0         0 my $tmpl = $self->prepare_template( $package, $subs_needing_tests );
144 0 0       0 $tmpl->param( update => %$existing_test_subs ? 1 : 0 );
145              
146 0         0 my $existing_content = $self->get_updated_calls( $subs_needing_tests );
147              
148 0         0 return $existing_content . $tmpl->output;
149             }
150              
151             sub get_updated_calls {
152 2     2 0 17 my $self = shift;
153 2         2 my $functions = shift;
154              
155 2 100 66     44 if ( defined $self->{ test_file } && -e $self->{ test_file } ) {
156 1         4 local $/;
157 1         8 open my $fh, '<', $self->{ test_file };
158 1         224 my $content = <$fh>;
159 1         5 close $fh;
160              
161 1         1019 my $missing_calls = '';
162 1         3 foreach my $fun ( @$functions ) {
163 2         8 $missing_calls .= sprintf "test_%s();\n", $fun->{ function };
164             }
165              
166 1         12 $content =~ s/^done_testing/$missing_calls\ndone_testing/ms;
167 1         15 return $content;
168             }
169             else {
170 1         4 return '';
171             }
172             }
173              
174             sub write_test_file {
175 0     0 0 0 my $self = shift;
176 0         0 my $content = shift;
177              
178 0         0 open my $fh, '>', $self->{ test_file };
179 0         0 print $fh $content;
180 0         0 close $fh;
181             }
182              
183             sub prepare_template {
184 1     1 0 7 my $self = shift;
185 1         2 my $package = shift;
186 1         2 my $functions = shift;
187              
188 1         11 my $tmpl = HTML::Template->new(
189             filehandle => \*DATA,
190             global_vars => 1,
191             die_on_bad_params => 0,
192             );
193 1         949 $tmpl->param( package => $package );
194 1         41 $tmpl->param( functions => $functions );
195              
196 1         22 return $tmpl;
197             }
198              
199             sub prepare_test_file_path {
200 3     3 0 95 my $self = shift;
201              
202 3         106 my $dir = dirname( $self->{ test_file } );
203 3         12 _debug( "dirname of $self->{ test_file } is $dir." );
204              
205 3 100       90 unless ( -d $dir ) {
206 2         7 _debug( "Making path $dir" );
207 2         441 make_path( $dir );
208             }
209             }
210              
211             sub get_package_functions {
212 4     4 0 1946 my $self = shift;
213 4         7 my $package = shift;
214 4         5 my $existing_test_subs = shift;
215              
216 4         19 _debug( "Trying to use package $package." );
217 1     1   7 eval "use $package";
  1     1   1  
  1     1   15  
  1     1   7  
  1         1  
  1         14  
  1         407  
  0         0  
  0         0  
  1         5  
  1         1  
  1         14  
  4         299  
218 4 100       17 if ( my $err = $@ ) {
219 1         8 die "could not 'use' package $package: $err";
220             }
221              
222 3         6 my $wanted_subs = [];
223 3         20 my $found_subs = Class::Inspector->function_refs( $package );
224 3         1549 foreach my $function ( @$found_subs ) {
225 207         1928 my $info = inspect( $function );
226 207         2023 my $name = $info->name;
227 207 100       18983 next if $name =~ m/^[[:upper:]_]+$/;
228 48 100 100     216 next if $name =~ m/^_/ && $self->{ skip_private_methods };
229 46 100       99 next if $info->package ne $package;
230              
231 28 100       2303 if ( ! $existing_test_subs->{ "test_$name" } ) {
232 27         152 push @$wanted_subs, { function => $name };
233             }
234             }
235              
236 3         19 return $wanted_subs;
237             }
238              
239             sub get_package_name {
240 2     2 0 10 my $self = shift;
241              
242 2         3 my $package = $self->{ package_file };
243 2 100       8 if ( $package =~ m/\./ ) {
244 1         3 _debug( 'Package provided by file path' );
245 1         9 open my $fh, '<', $self->{ package_file };
246 1         171 while ( my $ln = <$fh> ) {
247 1 50       5 if ( $ln =~ m/^package (.+);\s*$/ ) {
248 1         3 $package = $1;
249 1         31 last;
250             }
251             }
252             }
253 2         10 _debug( 'package name is ' . $package );
254              
255 2         10 return $package;
256             }
257              
258             sub _debug {
259 12 100   12   37 return unless $debug;
260 5         8 my @msgs = @_;
261 5         9 foreach ( @msgs ) {
262 5         496 print $_, "\n";
263             }
264             }
265              
266             sub analyze_t_file {
267 1     1 0 5 my $self = shift;
268              
269 1 50       24 if ( ! -e $self->{ test_file } ) {
270 0         0 return {};
271             }
272              
273 1         7 open my $fh, '<', $self->{ test_file };
274 1         2805 my $subs = {};
275 1         23 while ( my $ln = <$fh> ) {
276 170 100       425 if ( $ln =~ m/^\bsub\s+(\w+)/ ) {
277 10         37 $subs->{ $1 } = 1;
278             }
279             }
280              
281 1         13 return $subs;
282             }
283              
284             1;
285              
286             __DATA__