File Coverage

blib/lib/Test/Class/Tiny.pm
Criterion Covered Total %
statement 84 100 84.0
branch 24 40 60.0
condition 3 3 100.0
subroutine 13 13 100.0
pod 1 4 25.0
total 125 160 78.1


line stmt bran cond sub pod time code
1             package Test::Class::Tiny;
2              
3 3     3   668046 use strict;
  3         17  
  3         81  
4 3     3   16 use warnings;
  3         6  
  3         172  
5              
6             our $VERSION;
7             $VERSION = '0.01_01';
8              
9             =encoding utf-8
10              
11             =head1 NAME
12              
13             Test::Class::Tiny - xUnit in Perl, simplified
14              
15             =head1 SYNOPSIS
16              
17             package t::mytest;
18              
19             use parent qw( Test::Class::Tiny );
20              
21             __PACKAGE__->runtests() if !caller;
22              
23             sub T_startup_something {
24             # Runs at the start of the test run.
25             }
26              
27             sub T_setup_something {
28             # Runs before each normal test function
29             }
30              
31             # Expects 2 assertions:
32             sub T2_normal {
33             ok(1, 'yes');
34             ok( !0, 'no');
35             }
36              
37             # Ignores assertion count:
38             sub T0_whatever {
39             ok(1, 'yes');
40             }
41              
42             sub T_teardown_something {
43             # Runs after each normal test function
44             }
45              
46             sub T_shutdown_something {
47             # Runs at the end of the test run.
48             }
49              
50             =head1 DESCRIPTION
51              
52             L has served Perl’s xUnit needs for a long time
53             but is incompatible with the L framework. This module allows for
54             a similar workflow but in a way that works with both L and the older,
55             L-based modules.
56              
57             =head1 HOW (AND WHY) TO USE THIS MODULE
58              
59             xUnit encourages well-designed tests by encouraging creation of independent
60             chunks of test logic rather than a single monolithic block of code.
61             xUnit provides standard hooks for:
62              
63             =over
64              
65             =item * startup: The start of all tests
66              
67             =item * setup: The start of an individual test group (i.e., Perl function)
68              
69             =item * teardown: The end of an individual test group
70              
71             =item * shutdown: The end of all tests
72              
73             =back
74              
75             To write functions that execute at these points in the workflow,
76             name those functions with the prefixes C, C,
77             C, or C.
78              
79             To write a test function—i.e., a function that actually run some
80             tests—prefix the function name with C, the number of test assertions
81             in the function, then an underscore. For example, a function that contains
82             9 assertions might be named C. If that function
83             doesn’t run exactly 9 assertions, a test failure is produced.
84              
85             (To forgo counting test assertions, use 0 as the test count, e.g.,
86             C.)
87              
88             The above convention is a significant departure from L,
89             which uses Perl subroutine attributes to indicate this information.
90             Using method names is dramatically simpler to implement and also easier
91             to type.
92              
93             In most other respects this module attempts to imitate L.
94              
95             =head1 TEST INHERITANCE
96              
97             Like L, this module seamlessly integrates inherited methods.
98             To have one test module inherit another module’s tests, just make that
99             first module a subclass of the latter.
100              
101             B Inheritance in tests, while occasionally useful, can also
102             make for difficult maintenance over time if overused. Where I’ve found it
103             most useful is cases like L, where each test needs to run with
104             each backend implementation.
105              
106             =head1 RUNNING YOUR TEST
107              
108             To use this module to write normal Perl test scripts, just define
109             the script’s package (ideally not C
, but it’ll work) as a subclass of
110             this module. Then put the following somewhere in the script:
111              
112             __PACKAGE__->runtests() if !caller;
113              
114             Your test will thus execute as a “modulino”.
115              
116             =head1 SPECIAL FEATURES
117              
118             =over
119              
120             =item * As in L, a C method may be defined. If this
121             method returns truthy, then the class’s tests are skipped, and that truthy
122             return is given as the reason for the skip.
123              
124             =item * The C environment variable is honored as in L.
125              
126             =item * L’s C method is NOT recognized
127             here because an early return will already trigger a failure.
128              
129             =item * Within a test method, C may be called to retrieve the
130             number of expected test assertions.
131              
132             =item * To define a test function whose test count isn’t known until runtime,
133             name it B the usual C prefix, then at runtime do:
134              
135             $test_obj->num_method_tests( $name, $count )
136              
137             See F in the distribution for an example of this.
138              
139             =back
140              
141             =head1 SEE ALSO
142              
143             Besides L, you might also look at the following:
144              
145             =over
146              
147             =item * L also implements xUnit for L but doesn’t
148             allow inheritance.
149              
150             =item * L works with L, but the L requirement
151             makes use in CPAN modules problematic.
152              
153             =back
154              
155             =head1 AUTHOR
156              
157             Copyright 2019 L (FELIPE)
158              
159             =head1 LICENSE
160              
161             This code is licensed under the same license as Perl itself.
162              
163             =cut
164              
165             #----------------------------------------------------------------------
166              
167 3     3   16 use mro ();
  3         8  
  3         37  
168              
169 3     3   12 use Test2::API ();
  3         5  
  3         66  
170              
171             #----------------------------------------------------------------------
172              
173 3     3   14 use constant SKIP_CLASS => ();
  3         7  
  3         2286  
174              
175 3     3 0 1363 sub new { bless {}, shift }
176              
177             sub num_tests {
178 1     1 1 31 my ($self) = @_;
179              
180 1 50       4 if (!$self->{'_running'}) {
181 0         0 die "num_tests() called outside of running test!";
182             }
183              
184 1         5 return $self->{'_num_tests'};
185             }
186              
187             sub num_method_tests {
188 1     1 0 10 my ($self, $name, $count) = @_;
189              
190 1 50       5 die 'need name!' if !$name;
191              
192 1 50       4 if (@_ == 2) {
193 0         0 return $self->{'test'}{$name};
194             }
195              
196 1         6 $self->{'test'}{$name}{'count'} = $count;
197 1         3 $self->{'test'}{$name}{'simple_name'} = $name;
198              
199 1         3 return $self;
200             }
201              
202             sub runtests {
203 3     3 0 14 my ($self) = @_;
204              
205 3         14 local $self->{'_running'} = 1;
206              
207             # Allow calls as either instance or object method.
208 3 50       13 if (!ref $self) {
209 0         0 my $obj = $self->new();
210 0         0 $self = $obj;
211             }
212              
213 3         12 my $ctx = Test2::API::context();
214              
215 3 50       11799 if (my $reason = $self->SKIP_CLASS()) {
216 0         0 my $ctx = Test2::API::context();
217 0         0 $ctx->plan(1);
218 0         0 $ctx->skip( ref($self), $reason );
219             }
220             else {
221 3         23 $self->_analyze();
222              
223 3 50       15 if ( my $startup_hr = $self->{'startup'} ) {
224 0         0 $self->_run_funcs($startup_hr);
225             }
226              
227 3         12 my $setup_hr = $self->{'setup'};
228 3         6 my $teardown_hr = $self->{'teardown'};
229              
230 3 50       22 if ( my $tests_hr = $self->{'test'} ) {
231 3         20 for my $fn (sort keys %$tests_hr) {
232              
233 8 50       36 if (my $ptn = $ENV{'TEST_METHOD'}) {
234 0 0       0 next if $fn !~ m<$ptn>;
235             }
236              
237 8 50       20 if ($ENV{'TEST_VERBOSE'}) {
238 0         0 $ctx->diag( $/ . ref($self) . "->$fn()" );
239             }
240              
241 8         24 $self->_run_funcs($setup_hr);
242              
243 8         26 my $hub = $ctx->hub();
244              
245 8         34 my $got_count = 0;
246              
247             $hub->listen( sub {
248 50     50   13658 my ($hub, $event, $number) = @_;
249              
250 50 100       128 $got_count++ if $event->increments_count();
251              
252 50 100 100     820 if ($event->can('name') && !defined $event->name()) {
253 3         22 my $name = $tests_hr->{$fn}{'simple_name'};
254 3         11 $name =~ tr<_>< >;
255 3         9 $event->set_name($name);
256             }
257 8         66 } );
258              
259 8         132 my $want_count = $tests_hr->{$fn}{'count'};
260              
261 8         20 local $self->{'_num_tests'} = $want_count;
262              
263 8         24 local $@;
264 8 50       16 eval { $self->$fn(); 1 } or do {
  8         29  
  8         335  
265 0         0 my $err = $@;
266 0         0 $ctx->fail("$fn()", "Caught exception: $err");
267             };
268              
269 8 100       20 if ($want_count) {
270 7 50       20 if ($want_count != $got_count) {
271 0         0 $ctx->fail("Test count mismatch: got $got_count, expected $want_count");
272             }
273             }
274              
275 8         21 $self->_run_funcs($teardown_hr);
276             }
277             }
278              
279 3 50       23 if ( my $shutdown_hr = $self->{'shutdown'} ) {
280 0         0 $self->_run_funcs($shutdown_hr);
281             }
282             }
283              
284 3         17 $ctx->done_testing();
285              
286 3         205 $ctx->release();
287              
288 3         90 return;
289             }
290              
291             sub _analyze {
292 3     3   9 my ($self) = @_;
293              
294 3 50       20 if (!$self->{'_analyzed'}) {
295 3         8 my @isa = @{ mro::get_linear_isa(ref $self) };
  3         27  
296              
297 3         9 for my $ns (@isa) {
298 8         14 my $ptbl_hr = do {
299 3     3   21 no strict 'refs';
  3         6  
  3         942  
300 8         9 \%{"${ns}::"};
  8         34  
301             };
302              
303 8         90 for my $name (keys %$ptbl_hr) {
304 398 100       951 next if !$self->can($name);
305 381 100       770 next if $name !~ m<\AT(_setup|_teardown|_startup|_shutdown|[0-9]+)_(.+)>;
306              
307 7         16 my $whatsit = $1;
308 7         12 my $simple_name = $2;
309              
310 7 50       20 if ( $whatsit =~ s<\A_><>) {
311 0         0 $self->{$whatsit}{$name} = undef;
312             }
313             else {
314 7         30 $self->{'test'}{$name} = {
315             count => $whatsit,
316             simple_name => $simple_name,
317             };
318             }
319             }
320             }
321              
322 3         9 $self->{'_analyzed'} = 1;
323             }
324              
325 3         20 return;
326             }
327              
328             sub _run_funcs {
329 16     16   30 my ($self, $funcs_hr) = @_;
330              
331 16         47 for my $fn (sort keys %$funcs_hr) {
332 0         0 $funcs_hr->{$fn}->($self);
333             }
334              
335 16         42 return;
336             }
337              
338             1;