forked from houseabsolute/test-class-moose
/
Tutorial.pm
353 lines (257 loc) · 11.5 KB
/
Tutorial.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
package Test::Class::Moose::Tutorial;
# ABSTRACT: A starting guide for Test::Class::Moose
use 5.10.0;
our $VERSION = '0.67';
# there is no code here, but we're moving this from .pod to .pm to try to work
# around a strange bug where this is showing up instead of main docs on
# metacpan and cpan
1;
__END__
=head1 Getting Started
Automated testing is wonderful. Verifying your program's correctness in all
possible ways is a good thing that will save you time (and programmer time is
money).
Procedural tests like C<Test::More> are a good, general way to write tests for
all kinds of things. However, it is not very good when you're trying describe
relationships between tests. For this, a class-based test would work better,
because you could use the standard OO-techniques for describing object
relationships like inheritance.
When testing objects, it's good for code re-use to have test classes that match
the relationships between the regular objects. By creating test classes with
the same relationships, you can quickly increase test coverage by testing the
base class, and all the child classes can inherit those tests!
=head2 A Test Class
The first and most crucial part of using C<Test::Class::Moose> is a class that
runs some tests. C<Test::Class::Moose> loads a few modules for you
automatically, so the boilerplate is, at minimum:
package TestsFor::My::Test::Class;
use Test::Class::Moose;
C<Test::Class::Moose> loads C<strict>, C<warnings>, C<Moose>, and
C<Test::Most> (which includes C<Test::More>, C<Test::Deep>,
C<Test::Exception>, and C<Test::Differences>).
I put my test classes in the C<t/lib/TestsFor> directory, to keep the
separated from my other classes that help testing (C<t/lib>) and my other test
scripts. This is just a convention; the directory can be anything you want it
to be, but it is a good idea to keep your test classes separate from your
other test-related modules.
Now, we need a method that runs our test. C<Test::Class::Moose> test
methods start with C<test_>. Any method that starts with C<test_> will be run
as a test.
use My::Module;
sub test_construction {
my $test = shift;
my $obj = My::Module->new;
isa_ok $obj, 'My::Module';
}
Every C<test_> method is run as a subtest, and no plan is required. We can have as
many C<test_> methods as we want.
=head2 A Test Runner
Now that we have a test class, we need a way for prove to load and run them.
C<Test::Class::Moose> can load our test modules from a given directory, and it
has a runtests() method that will run any test modules that have already been
loaded.
# t/test_class_tests.t
use File::Spec::Functions qw( catdir );
use FindBin qw( $Bin );
use Test::Class::Moose::Load qw( catdir( $Bin, 't', 'lib' ) );
Test::Class::Moose->new->runtests;
Or if you're not worried about the portability of that directory:
use Test::Class::Moose::Load 't/lib';
Test::Class::Moose->new->runtests;
This test script will load all of the C<Test::Class::Moose> modules inside
C<t/lib/> and then run them. All your test modules get run by this one script,
but since they're run as subtests, you will get a report on how many test
classes failed.
We can run our test script using prove. I'll turn on verbose output (-v) to
show you what the TAP output looks like
prove -v t/test_class_tests.t
t/test_class_tests.t ..
1..1
#
# Running tests for TestsFor::My::Class
#
1..1
# TestsFor::My::Class->test_something()
ok 1 - I tested something!
1..1
ok 1 - test_something
ok 1 - TestsFor::My::Class
ok
All tests successful.
Files=1, Tests=1, 0 wallclock secs ( 0.03 usr 0.01 sys + 0.34 cusr 0.01 csys = 0.39 CPU)
Result: PASS
=head1 Event Hooks
There are various points in the test script where we might want to perform
some actions: Reset a test database, create a temp file, or otherwise set up
prerequisites for a test. C<Test::Class::Moose> provides some hooks that allow
us to perform actions at these points.
=head2 test_startup / test_shutdown
C<test_startup> is run as the very first thing in our test class, and is run only
once per test class. This allows us to set up some global things, like a
database that will be used throughout the entire test.
C<test_shutdown> is run once as the very last thing in our test class, and is
run only once per test class. This allows us to clean up things from
C<test_startup>, and also test to verify that anything from C<test_startup>
looks exactly as it should before we clean it up.
=head2 test_setup / test_teardown
What C<test_startup> and C<test_shutdown> are for the entire test class,
C<test_setup> and C<test_teardown> are for every single C<test_*> method.
C<test_setup> is run before every test method. For canonical unit testing,
this is where you can create the things you need for each test, such as a log
file, rebuilding fixtures, or starting a database transaction.
C<test_teardown> happens after every test, and is where you can clean up the
things created in C<test_setup>, such as ending the database transaction. Note
that some developers actually prefer their cleanup to happen in their
C<test_setup> method, prior to setting up the test. That sounds odd, but it
means that if a test method fails, you haven't yet cleaned up and can easily
inspect your test environment.
=head1 Test Class Composition
The most important reason to choose a class test over a procedural test (using
only C<Test::More>) is class composition.
=head2 Inheritance
Since we're using C<Moose>, inheritance is as easy as:
package TestsFor::My::Test::Class;
use Test::Class::Moose;
extends 'My::Test::Base';
C<Test::Class::Moose> even provides a shortcut:
package TestsFor::My::Text::Class;
use Test::Class::Moose extends => 'My::Test::Base';
If C<My::Test::Base> will not be testing anything itself, we do not put it in
C<t/lib/TestsFor>, instead we put it in C<lib> or C<t/lib> (depending on if we
want it to be part of the public set of modules or not). This will make sure
our test runner does not try to run our base class that doesn't test anything
concrete.
=head2 Roles
If your distribution uses roles, so should your tests. Like inheritance, roles
are added in the regular C<Moose> way:
package TestsFor::My::Test::Class;
use Test::Class::Moose;
with 'My::Test::Role';
If you want your role to also provide tests, make sure you use
L<Test::Class::Moose::Role> instead of C<Moose::Role>.
=head2 Organizing Your Tests
Test code should be held to the same standard as the rest of the code in your
distribution:
=over 4
=item Don't Repeat Yourself
Copy/paste code isn't okay in your module code, and it should not be okay in
your test code either! Refactor your tests to use roles or inheritance.
=back
=head1 Advanced Features
=head2 plan
If you need to prepare a plan for your tests, you can do so using the plan()
method:
sub test_constructor {
my $test = shift;
$test->test_report->plan( 1 ); # 1 test in this sub
isa_ok My::Module->new, 'My::Module';
}
Using the C<plan()> method, we can know exactly how many tests did not run if
the test method ends prematurely, or how many extra tests were run if we had
too many tests.
Alternately, you can use the C<Test> (a single test) or C<Tests> attributes
to set the plan. If you do this, the method is marked as a test method even if
it does not begin with C<test_>.
# 'Test' asserts a plan of 1 test
sub test_constructor : Test {
my $test = shift;
isa_ok My::Module->new, 'My::Module';
}
# 'Tests' means multiple tests with no plan (note the test name)
sub a_test_method : Tests {
# many tests here
}
# 'Tests($integer) means a plan of $integer
sub this_is_another_test : Tests(3) {
# 3 tests
}
=head2 skip
We can use the C<test_startup> and C<test_setup> methods to skip tests that we
can't or don't want to run for whatever reason.
If we don't want to run a single test method, we can use the C<test_setup> method
and call the C<test_skip> method with the reason we're skipping the test.
sub test_will_fail {
my ( $test ) = @_;
fail "This doesn't work!";
}
sub test_setup {
my $test = shift;
if ( $test->test_report->current_method->name eq 'test_will_fail' ) {
$test->test_skip( 'It doesn't work' );
}
}
If we don't want to run an entire class, we can use the C<test_startup> method
and the same C<test_skip> method with the reason we're skipping the test.
sub test_startup {
my $test = shift;
$test->test_skip( "The entire class doesn't work" );
}
=head2 Run Specific Test Classes
One of the problems with having only one test script to run all the test
classes is when we're working directly with one test class we still have to
run all the other test classes.
To fix this problem, C<Test::Class::Moose> allows us to specify which specific
classes we want to run in its constructor:
# t/test_class_tests.t
use File::Spec::Functions qw( catdir );
use FindBin qw( $Bin );
use Test::Class::Moose::Load catdir( $Bin, 't', 'lib' );
Test::Class::Moose->new(
classes => [ 'TestsFor::My::Test::Class' ],
)->runtests;
Now, we only run C<TestsFor::My::Test::Class> instead of all the tests found in
C<TestsFor::>.
This isn't very elegant though, since we have to edit C<t/test_class_tests.t>
every time we want to run a new test. So, C<Test::Class::Moose> can also
accept which test classes to run via C<@ARGV>:
# t/test_class_tests.t
use File::Spec::Functions qw( catdir );
use FindBin qw( $Bin );
use Test::Class::Moose::Load catdir( $Bin, 't', 'lib' );
Test::Class::Moose->new( classes => \@ARGV )->runtests;
If C<@ARGV> is empty, C<Test::Class::Moose> will run all classes. To give
arguments while running C<prove>, we use the arisdottle C<::>:
prove -lb t/test_class_tests.t :: My::Test::Class
Now we can choose which test class we want to run right on the command line.
=head1 Tags
Tags are a way of organizing your test methods into groups. Later you can
choose to only execute the test methods from one or more tags. You can add
tags like "online" for tests that require a network, or "database" for tests
that require a database, and then include or exclude those tags when you
execute your tests.
You add tags to your test methods using attributes. A test method may have one
or more tags:
sub test_database : Tags( database ) { ... }
sub test_network : Tests(7) Tags( online api ) { ... }
Then, if your database goes down, you can exclude those tests from the
C<t/test_class_tests.t> script:
# t/test_class_tests.t
use File::Spec::Functions qw( catdir );
use FindBin qw( $Bin );
use Test::Class::Moose::Load catdir( $Bin, 't', 'lib' );
Test::Class::Moose->new(
classes => \@ARGV,
exclude_tags => [qw( database )],
)->runtests;
By adding tags to your tests, you can run only those tests that you
absolutely need to, increasing your productivity.
=head1 Boilerplate
Here is the bare minimum you need to get started using C<Test::Class::Moose>
=head2 Test Class
# t/lib/TestsFor/My/Class.pm
package TestsFor::My::Class;
use Test::Class::Moose;
sub test_something {
pass "I tested something!";
}
1;
=head2 Test Runner
# t/test_class_tests.t
use File::Spec::Functions qw( catdir );
use FindBin qw( $Bin );
use Test::Class::Moose::Load catdir( $Bin, 't', 'lib' );
Test::Class::Moose->new(
classes => \@ARGV,
)->runtests;
=head1 AUTHOR
Doug Bell: https://github.com/preaction