1package Test2::Manual::Tooling::Nesting;
2use strict;
3use warnings;
4
5our $VERSION = '0.000143';
6
71;
8
9__END__
10
11=head1 NAME
12
13Test2::Manual::Tooling::Nesting - Tutorial for using other tools within your
14own.
15
16=head1 DESCRIPTION
17
18Sometimes you find yourself writing the same test pattern over and over, in
19such cases you may want to encapsulate the logic in a new test function that
20calls several tools together. This sounds easy enough, but can cause headaches
21if not done correctly.
22
23=head1 NAIVE WAY
24
25Lets say you find yourself writing the same test pattern over and over for multiple objects:
26
27    my $obj1 = $class1->new;
28    is($obj1->foo, 'foo', "got foo");
29    is($obj1->bar, 'bar', "got bar");
30
31    my $obj2 = $class1->new;
32    is($obj2->foo, 'foo', "got foo");
33    is($obj2->bar, 'bar', "got bar");
34
35    ... 10x more times for classes 2-12
36
37The naive way to do this is to write a C<check_class()> function like this:
38
39    sub check_class {
40        my $class = shift;
41        my $obj = $class->new;
42        is($obj->foo, 'foo', "got foo");
43        is($obj->bar, 'bar', "got bar");
44    }
45
46    check_class($class1);
47    check_class($class2);
48    check_class($class3);
49    ...
50
51This will appear to work fine, and you might not notice any problems,
52I<so long as the tests are passing.>
53
54=head2 WHATS WRONG WITH IT?
55
56The problems with the naive approach become obvious if things start to fail.
57The diagnostics that tell you what file and line the failure occurred on will be
58wrong. The failure will be reported to the line I<inside> C<check_class>, not
59to the line where C<check_class()> was called. This is problem because it
60leaves you with no idea which class is failing.
61
62=head2 HOW TO FIX IT
63
64Luckily this is extremely easy to fix. You need to acquire a context object at
65the start of your function, and release it at the end... yes it is that simple.
66
67    use Test2::API qw/context/;
68
69    sub check_class {
70        my $class = shift;
71
72        my $ctx = context();
73
74        my $obj = $class->new;
75        is($obj->foo, 'foo', "got foo");
76        is($obj->bar, 'bar', "got bar");
77
78        $ctx->release;
79    }
80
81See, that was easy. With these 2 additional lines we know have proper file+line
82reporting. The nested tools will find the context we acquired here, and know to
83use it's file and line numbers.
84
85=head3 THE OLD WAY (DO NOT DO THIS ANYMORE)
86
87With L<Test::Builder> there was a global variables called
88C<$Test::Builder::Level> which helped solve this problem:
89
90    sub check_class {
91        my $class = shift;
92
93        local $Test::Builder::Level = $Test::Builder::Level + 1;
94
95        my $obj = $class->new;
96        is($obj->foo, 'foo', "got foo");
97        is($obj->bar, 'bar', "got bar");
98    }
99
100This variable worked well enough (and will still work) but was not very
101discoverable. Another problem with this variable is that it becomes cumbersome
102if you have a more deeply nested code structure called the nested tools, you
103might need to count stack frames, and hope they never change due to a third
104party module. The context solution has no such caveats.
105
106=head1 SEE ALSO
107
108L<Test2::Manual> - Primary index of the manual.
109
110=head1 SOURCE
111
112The source code repository for Test2-Manual can be found at
113F<https://github.com/Test-More/Test2-Suite/>.
114
115=head1 MAINTAINERS
116
117=over 4
118
119=item Chad Granum E<lt>exodist@cpan.orgE<gt>
120
121=back
122
123=head1 AUTHORS
124
125=over 4
126
127=item Chad Granum E<lt>exodist@cpan.orgE<gt>
128
129=back
130
131=head1 COPYRIGHT
132
133Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
134
135This program is free software; you can redistribute it and/or
136modify it under the same terms as Perl itself.
137
138See F<http://dev.perl.org/licenses/>
139
140=cut
141