stas 01/10/20 03:48:33 Modified: src/devel/writing_tests writing_tests.pod Log: - mostly rewriting the "how to write tests" section - various fixes in the other sections - starting the debug section Revision Changes Path 1.13 +451 -101 modperl-docs/src/devel/writing_tests/writing_tests.pod Index: writing_tests.pod =================================================================== RCS file: /home/cvs/modperl-docs/src/devel/writing_tests/writing_tests.pod,v retrieving revision 1.12 retrieving revision 1.13 diff -u -r1.12 -r1.13 --- writing_tests.pod 2001/10/07 13:52:20 1.12 +++ writing_tests.pod 2001/10/20 10:48:33 1.13 @@ -1,13 +1,13 @@ -=head1 Developing and Running Tests with C<Apache::Test> Framework +=head1 Developing and Running Tests with the C<Apache::Test> Framework =head1 Introduction This chapter is talking about the C<Apache::Test> framework, and in -particular explains: +particular explains how to: =over -=item * how to run existing tests +=item * run existing tests =item * setup a testing environment @@ -27,7 +27,7 @@ therefore enjoyable process. If you have ever written or looked at the tests most Perl modules come -with, C<Apache::Test> uses the same concept. The script C<t/TEST> is +with, C<Apache::Test> uses the same concept. The script I<t/TEST> is running all the files ending with I<.t> it finds in the I<t/> directory. When executed a typical test prints the following: @@ -39,7 +39,7 @@ Every C<ok> or C<not ok> is followed by the number which tells which sub-test has succeeded or failed. -C<t/TEST> uses the C<Test::Harness> module which intercepts the +I<t/TEST> uses the C<Test::Harness> module which intercepts the C<STDOUT> stream, parses it and at the end of the tests print the results of the tests running: how many tests and sub-tests were run, how many succeeded, skipped or failed. @@ -78,7 +78,7 @@ mode and send you back the report. It'll be much easier to understand what the problem is if you get these debug printings from the user. -In the section L<"Using Apache::TestUtil"> we discuss a few helper +In the section L<"How to Write Tests"> we discuss a few helper functions which make the tests writing easier. For more details about the C<Test::Harness> module please refer to its @@ -191,18 +191,6 @@ META: do we include it in modperl-2.0? +document the new syntax. -<ToGo when the issue with Reload is resolved> -Or use this trick: - - PerlModule Apache::Foo - <Location /cgi-test> - PerlOptions +GlobalRequest - SetHandler modperl - PerlResponseHandler "sub { delete $INC{'Apache/Foo.pm'}; require Apache::Foo; Apache::Foo::handler(shift);}" - </Location> - -</ToGo> - This will force the response handler C<Apache::Foo> to be reloaded on every request. @@ -519,7 +507,7 @@ how amazingly it works and how amazingly it can be deployed by other users. -=head1 How to Write Tests +=head1 Apache::Test Framework's Architecture In the previous section we have written a basic test, which doesn't do much. In the following sections we will explain how to write more @@ -817,159 +805,482 @@ =back -=head1 Developing Tests: Gory Details - - - -=head2 Writing Test Methodology +=head1 How to Write Tests -META: to be written +All the communications between tests and C<Test::Harness> which +executes them is done via STDOUT. I.e. whatever tests want to report +they do by printing something to STDOUT. If a test wants to print some +debug comment it should do it on a separate line starting with +C<#>. -=head2 Using C<Apache::TestUtil> -META: to be written -Here we cover in details some features useful in writing tests: +=head2 Defining How Many Sub-Tests Are to Be Run -=head2 Apache::Test functions +Before sub-tests of a certain test can be run it has to declare how +many sub-tests it is going to run. In some cases the test may decide +to skip some of its sub-tests or not to run any at all. Therefore the +first thing the test has to print is: -B<Apache::Test> is a wrapper around the standard I<Test.pm> with -helpers for testing an Apache server. + 1..M\n -META: merge with Apache::Test's inlined scarce docs +where M is a positive integer. So if the test plans to run 5 sub-tests +it should do: -=over + print "1..5\n"; -=item * ok() +In C<Apache::Test> this is done as follows: -Same as I<Test::ok>, see I<Test.pm> documentation. -META: expand + use Apache::Test; + plan tests => 5; -=item * skip() -Same as I<Test::skip>, see I<Test.pm> documentation. -META: expand -=item * sok() +=head2 Skipping a Whole Test -META: to be written +Sometimes when the test cannot be run, because certain prerequisites +are missing. To tell C<Test::Harness> that the whole test is to be +skipped do: -=item * plan() + print "1..0 # skipped because of foo is missing\n"; -Whenever you start a new test, you have to declare how many sub-tests -it includes. This is done easily with: +The optional comment after C<# skipped> will be used as a reason for +test's skipping. Under C<Apache::Test> the optional last argument to +the plan() function can be used to define prerequisites and skip the +test: use Apache::Test; - plan tests => 10; # run 10 tests - -Now if you want to skip the whole test use the third argument to plan(): - - plan tests => $ntests, \&condition; - -if condition() returns false, the whole test is skipped. For example -if some optional feature relying on 3rd party module is tested and it -cannot be found on user's system, you can say + plan tests => 5, $test_skipping_prerequisites; - plan tests => $ntests, have_module 'Chatbot::Eliza'; +This last argument can be: -here have_module() is used to test whether C<Chatbot::Eliza> is -installed. - -plan() is a wrapper around C<Test::plan>. - -C<Test::plan> accepts a hash C<%arg> as its arguments, therefore -C<Apache::Test::plan> extends C<Test::plan>'s functionality, by -allowing yet another argument after the normal hash. If this argument -is supplied -- it's used to decide whether to continue with the test -or to skip it all-together. This last argument can be: - =over =item * a C<SCALAR> -the test is skipped if the scalar has a false value. +the test is skipped if the scalar has a false value. For example: + plan tests => 5, 0; + =item * an C<ARRAY> reference have_module() is called for each value in this array. The test is skipped if have_module() returns false (which happens when at least -one C or Perl module from the list cannot be found). +one C or Perl module from the list cannot be found). For example: + + plan tests => 5, [qw(mod_index mod_mime)]; =item * a C<CODE> reference -the tests will be skipped if the function returns false as we have -just seen. +the tests will be skipped if the function returns a false value. For +example: -=back + plan tests => 5, \&have_lwp; -If the first argument to plan() is an object, such as an -C<Apache::RequestRec> object, C<STDOUT> will be tied to it. +the test will be skipped if LWP is not available + +=back -The I<Test.pm> global state will also be refreshed by calling -C<Apache::Test::test_pm_refresh>. +There is a number of useful functions whose return value can be used +as a last argument for plan(): -All other arguments are passed through to I<Test::plan>. +=over =item * have_module() have_module() tests for existance of Perl modules or C modules -I<mod_*>. Accepts a list of modules or a reference to the -list. Returns FALSE if at least one of the modules is not found, -returns true otherwise. +I<mod_*>. It accepts a list of modules or a reference to the list. If +at least one of the modules is not found it returns a false value, +otherwise it returns a true value. For example: + + plan tests => 5, have_module qw(Chatbot::Eliza Apache::AI); + +will skip the whole test if both Perl modules C<Chatbot::Eliza> and +C<Apache::AI> are not available. =item * have_perl() have_perl('foo') checks whether the value of C<$Config{foo}> or -C<$Config{usefoo}> is equal to 'define'. So one can tell: +C<$Config{usefoo}> is equal to I<'define'>. For example: plan tests => 2, have_perl 'ithreads'; -and if the Perl wasn't compiled with C<-Duseithreads> the condition -will be false and the test will be skipped. +if Perl wasn't compiled with C<-Duseithreads> the condition will be +false and the test will be skipped. =item * have_lwp() -META: to be written +Tests whether the Perl module LWP is installed. =item * have_http11() -META: to be written +Tries to tell LWP that sub-tests need to be run under HTTP 1.1 +protocol. Fails if the installed version of LWP is not capable of +doing that. =item * have_cgi() -META: to be written +tests whether mod_cgi or mod_cgid is available. =item * have_apache() -META: to be written +tests for a specific version of httpd. For example: + + plan tests => 2, have_apache 2; +will skip the test if not run under httpd 2.x. =back + + +=head2 Reporting a Success or a Failure of Sub-tests + +After printing the number of planned sub-tests, and assuming that the +test is not skipped, the tests is running its sub-tests and each +sub-test is expected to report its success or failure by printing +I<ok> or I<not ok> respectively followed by its sequential number and +a new line. For example: + + print "ok 1\n"; + print "not ok 2\n"; + print "ok 3\n"; + +In C<Apache::Test> this is done using the ok() function which prints +I<ok> if its argument is a true value, otherwise it prints I<not +ok>. In addition it keeps track of how many times it was called, and +every time it prints an incremental number, therefore you can move +sub-tests around without needing to remember to adjust sub-test's +sequential number, since now you don't need them at all. For example +this test snippet: + + use Apache::Test; + plan tests => 3; + ok "success"; + print "# expecting to fail next test\n" + ok ""; + ok 0; + +will print: + + 1..3 + ok 1 + # expecting to fail next test + not ok 2 + not ok 3 + +Most of the sub-tests perform one of the following things: + +=over + +=item * + +test whether some variable is defined: + + ok defined $object; + +=item * + +test whether some variable is a true value: + + ok $value; + +or a false value: + + ok !$value; + +=item * + +test whether a received from somewhere value is equal to an expected +value: + + $expected = "a good value"; + $received = get_value(); + ok defined $received && $received eq $expected; + +=back + + + + + + +=head2 Skipping Sub-tests + +If the standard output line contains the substring I< # Skip> (with +variations in spacing and case) after I<ok> or I<ok NUMBER>, it is +counted as a skipped test. C<Test::Harness> reports the text after I< +# Skip\S*\s+> as a reason for skipping. So you can count a sub-test as +a skipped as follows: + + print "ok 3 # Skip for some reason\n"; + +or using the C<Apache::Test>'s skip() function which works similarly +to ok(): + + skip $should_skip, $test_me; + +so if C<$should_skip> is true, the test will be reported as +skipped. The second argument is the one that's sent to ok(). + +C<Apache::Test> also allows to write tests in such a way that only +selected sub-tests will be run. The test simply needs to switch from +using ok() to sok(). Where the argument to sok() is a CODE reference +or a BLOCK whose return value will be passed to ok(). If sub-tests +are specified on the command line only those will be run/passed to +ok(), the rest will be skipped. If no sub-tests are specified, sok() +works just like ok(). For example, you can write this test: + + skip_subtest.t + -------------- + use Apache::Test; + plan tests => 4; + sok {1}; + sok {0}; + sok sub {'true'}; + sok sub {''}; + +and then ask to run only sub-tests 1 and 3 and to skip the rest. + + % ./t/TEST -v skip_subtest 1 3 + skip_subtest....1..4 + ok 1 + ok 2 # skip skipping this subtest + ok 3 + ok 4 # skip skipping this subtest + ok, 2/4 skipped: skipping this subtest + All tests successful, 2 subtests skipped. + +=head2 Todo Sub-tests + +In a safe fashion to skipping specific sub-tests, it's possible to +declare some sub-tests as I<todo>. This distinction is useful when we +know that some sub-test is failing but for some reason we want to flag +it as a todo sub-test and not as a broken test. C<Test::Harness> +recognizes I<todo> sub-tests if the standard output line contains the +substring I< # TODO> after i<not ok> or I<not ok NUMBER> and is +counted as a todo sub-test. The text afterwards is the explanation of +the thing that has to be done before this sub-test will succeed. For +example: + + print "not ok 42 # TODO not implemented\n"; + +In C<Apache::Test> this can be done with passing a reference to a list +of sub-tests numbers that should be marked as I<todo> sub-test: + + plan tests => 7, todo => [3, 6]; + +In this example sub-tests 3 and 6 will be marked as I<todo> sub-tests. + + + + + +=head2 Making it Easy to Debug + +Ideally we want all the tests to pass, reporting minimum noise or none +at all. But when some sub-tests fail we want to know the reason for +their failure. If you are a developer you can dive into the code and +easily find out what's the problem, but when you have a user who has a +problem with the test suite it'll make his and your life much easier +if you make it easy for the user to report you the exact problem. + +Usually this is done by printing the comment of what the sub-test +does, what is the expected value and what's the received value. This +is a good example of debug friendly sub-test: + + debug_comments.t + ---------------- + use Apache::Test; + plan tests => 1; + + print "# testing feature foo\n"; + $expected = "a good value"; + $received = "a bad value"; + print "# expected: $expected\n"; + print "# received: $received\n"; + ok defined $received && $received eq $expected; + +If in this example C<$received> gets assigned I<a bad value> string, +the test will print the following: + + % t/TEST debug_comments + debug_comments....FAILED test 1 + +No debug help here, since in a non-verbose mode the debug comments +aren't printed. If we run the same test using the verbose mode, +enabled with C<-v>: + + % t/TEST -v debug_comments + debug_comments....1..1 + # testing feature foo + # expected: a good value + # received: a bad value + not ok 1 + +we can see exactly what's the problem, by visual expecting of the +expected and received values. + +It's true that adding a few print statements for each sub tests is +cumbersome, and adds a lot of noise, when you could just tell: + + ok "a good value" eq "a bad value"; + +but no fear, C<Apache::TestUtil> comes to help. The function t_cmp() +does all the work for you: + + use Apache::Test; + use Apache::TestUtil; + ok t_cmp( + "a good value", + "a bad value", + "testing feature foo"); + +In addition it will handle undef'ined values as well, so you can do: + + ok t_cmp(undef, $expected, "should be undef"); + + + + + +=head2 Tie-ing STDOUT to a Response Handler Object + +It's possible to run the sub-tests in the response handler, and simply +return them as a response to the client which in turn will print them +out. Unfortunately in this case you cannot use ok() and other +functions, since they print and don't return the results, therefore +you have to do it manually. For example: + + sub handler { + my $r = shift; + + $r->print("1..2\n"); + $r->print("ok 1\n"); + $r->print("not ok 2\n"); + + return Apache::OK; + } + +now the client should print the response to STDOUT for +C<Test::Harness> processing. + +If the response handler is configured as: + + SetHandler perl-script + +C<STDOUT> is already tied to the request object C<$r>. Therefore you +can now rewrite the handler as: + + use Apache::Test; + sub handler { + my $r = shift; + + Apache::Test::test_pm_refresh(); + plan tests => 2; + ok "true"; + ok ""; + + return Apache::OK; + } + +However to be on the safe side you also have to call +Apache::Test::test_pm_refresh() allowing plan() and friends to be +called more than once per-process. + +Under different settings C<STDOUT> is not tied to the request object. +If the first argument to plan() is an object, such as an +C<Apache::RequestRec> object, C<STDOUT> will be tied to it. The +C<Test.pm> global state will also be refreshed by calling +C<Apache::Test::test_pm_refresh>. For example: + + use Apache::Test; + sub handler { + my $r = shift; + + plan $r, tests => 2; + ok "true"; + ok ""; + + return Apache::OK; + } + +Yet another alternative to handling the test framework printing inside +response handler is to use C<Apache::TestToString> class. + +The C<Apache::TestToString> class is used to capture C<Test.pm> output +into a string. Example: + + use Apache::Test; + sub handler { + my $r = shift; + + Apache::TestToString->start; + + plan tests => 2; + ok "true"; + ok ""; + + my $output = Apache::TestToString->finish; + $r->print($output); + + return Apache::OK; + } + +In this example C<Apache::TestToString> intercepts and buffers all the +output from C<Test.pm> and can be retrieved with its finish() +method. Which then can be printed to the client in one +shot. Internally it calls Apache::Test::test_pm_refresh() to make sure +plan(), ok() and other functions() will work correctly more than one +test is running under the same interpreter. + + + + + + =head2 Auto Configuration +If the test is comprised only from the request part, you have to +manually configure the targets you are going to use. This is usually +done in I<t/conf/extra.conf.in>. + +If your tests are comprised from the request and response parts, C<Apache::Test> automatically adds the configuration section for each -response part. If you put some configuration bits into the C<__DATA__> -section of the response part, which declares a package: +response handler it finds. For example for the response handler: + + package TestResponse::nice; + ... some code + 1; + +it will put into I<t/conf/httpd.conf>: + <Location /TestResponse::nice> + SetHandler modperl + PerlResponseHandler TestResponse::nice + </Location> + +If you want to add some extra configuration directives, use the +C<__DATA__> section, as in this example: + package TestResponse::nice; ... some code 1; __DATA__ - PerlRequire "Foo.pm" + PerlSetVar Foo Bar -these will be wrapped into the C<E<lt>LocationE<gt>> section and -placed into configuration file for you: +These directives will be wrapped into the C<E<lt>LocationE<gt>> +section and placed into I<t/conf/httpd.conf>: <Location /TestResponse::nice> - SetHandler modperl - PerlResponseHandler TestResponse::nice - PerlRequire "Foo.pm" + SetHandler modperl + PerlResponseHandler TestResponse::nice + PerlSetVar Foo Bar </Location> If some directives are supposed to go to the base configuration, -i.e. not to automatically wrapped into C<E<lt>LocationE<gt>> block, +i.e. not to be automatically wrapped into C<E<lt>LocationE<gt>> block, you should use a special C<E<lt>BaseE<gt>>..C<E<lt>/BaseE<gt>> block: __DATA__ @@ -989,15 +1300,15 @@ As you can see the C<E<lt>BaseE<gt>>..C<E<lt>/BaseE<gt>> block has gone. As you can imagine this block was added to support our virtue of -lazyness, since in most tests don't need to add directives to the base -configuration and we want to keep the configuration size in test -minimal and let Perl do the rest of the job for us. +laziness, since most tests don't need to add directives to the base +configuration and we want to keep the configuration sections in tests +to a minimum and let Perl do the rest of the job for us. META: Virtual host? META: to be completed -=head2 Tests with Non-threads perl versus threaded Perl +=head2 Threaded versus Non-threaded Perl Test's Compatibility Since the tests are supposed to run properly under non-threaded and threaded perl, you have to worry to enclose the threaded perl specific @@ -1014,8 +1325,8 @@ perl, therefore you have to write: <IfDefine PERL_USEITHREADS> - # a new interpreter pool - PerlOptions +Parent + # a new interpreter pool + PerlOptions +Parent </IfDefine> Just like the configuration, the test's code has to work for both @@ -1028,8 +1339,47 @@ which is essentially does a lookup in $Config{useithreads}. +=head1 Debugging Tests + +Sometimes your tests won't run properly or even worse will +segfault. There are cases where it's possible to debug broken tests +with simple print statements but usually it's very time consuming and +ineffective. Therefore it's a good idea to get yourself familiar with +Perl and C debuggers, and this knowledge will save you a lot of time +and grief in a long run. + +=head2 Under C debugger + +META: to be written + +=head2 Under Perl debugger + +When the Perl code misbehaves it's the best to run it under the Perl +debugger. Normally started as: + + % perl -d program.pl + +the flow control gets passed to the Perl debugger, which allows you to +run the program in single steps and examine its states and variables +after every executed statement. Of course you can set up breakpoints +and watches to skip irrelevant code sections and watch after certain +variables. The I<perldebug> and the I<perldebtut> manpages are +covering the Perl debugger in fine details. + +The C<Apache::Test> framework extends the Perl debugger and plugs in +C<LWP>'s debug features, so you can debug the requests. Let's take +test I<apache/read> from mod_perl 2.0 and present the features as we +go: + +META: to be completed + + +=head1 Writing Tests Methodology + +META: to be completed + -=head1 When Tests Should Be Written +=head2 When Tests Should Be Written =over
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]