Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Comment on

( #3333=superdoc: print w/ replies, xml ) Need Help??

Intro

Warning: if you have never wrote automated tests for your code, if you have no idea what about it and if you do not know why you want to write them then please read first An Introduction to Testing.

It is easy to write tests for code which doesn't have dependancies on external environment. When you write tests, say, for implementation of some mathematical algorithm you have easily controlled environment. Everything you have to worry about is supplying input values and comparing them with output. But how can you test code which, say, sends emails using Net::SMTP module? To satisfy requirement for controlled environment you will have to run test mail server which must be controlled by test code.

Often very simple technique of using fake intefaces may be much more easier alternative. Instead of running test mail server emulate Net::SMTP interface in test code. Emulation may actually do not send emails over network but pass information about sent emails to testing code.

I'll try to show this technique on example of Perl module which uses Apache API (so it expects mod_perl environment) and I'll show how it can be tested using emulated mod_perl environment.

Example Module

Example Perl module extends CGI::Cookie module. It's POD documentation should be self-explanatory.

=head1 NAME Apache::Perl::Cookie - interface to Netscape Cookies =head1 SYNOPSIS use Apache::Perl::Cookie; my %cookies = Apache::Perl::Cookie->fetch; $cookie = Apache::Perl::Cookie->new(-name => 'ID', -value => 1234); $cookie->bake; =head1 DESCRIPTION Apache::Perl::Cookie is subclass of L<CGI::Cookie|CGI::Cookie>. It uses Apache request object instead of enviroment variables to get existing cookies. Also it adds method for sending cookies using Apache request object. Note that this module serves as example only. In real mod_perl programs you more likely to find L<Apache::Cookie|Apache::Cookie> module useful. =head1 BUGS AND LIMITATIONS This module relies on mod_perl API so it cannot be used in non-mod_perl environment. =head1 SEE ALSO L<Apache|Apache> L<Apache::Cookie|Apache::Cookie> L<CGI::Cookie|CGI::Cookie> =cut package Apache::Perl::Cookie; use strict; use warnings; use base qw(CGI::Cookie); sub fetch { my $class = shift; my $header = Apache->request->header_in('Cookie'); if(defined $header) { return CGI::Cookie->parse($header); } return; } sub bake { my $self = shift; Apache->request->headers_out->add('Set-Cookie' => $self); } 1;

Test Script

As POD documentation says this module relies on mod_perl API. So how are we going to write tests for it without using Apache configured with mod_perl support?

It is much more easier to fake mod_perl environement instead of using Apache for automated tests. There is no need to emulate all mod_perl API. It is sufficient to emulate only parts of it which are used in the module. Apache emulation interface can be implemented using fake Apache module. I.e:

package Apache; sub request { my $class = shift; return bless {}, $class; } sub header_in { .... .... ....

But I'll use another method. One very useful module for building inteface emulations is Test::MockObject. Here example of test script which uses this module to fake mod_perl environment:

use Test::More tests => 9; use strict; use warnings; use Test::MockObject; # build mod_perl interface emulation my $COOKIE_HEADER_IN; my $COOKIE_HEADER_OUT; { my $headers_out = Test::MockObject->new; my $request = Test::MockObject->new; # Create 'request' method in package Apache which always returns # same fake request object $request. Test::MockObject->fake_module('Apache', request => sub { $request }); # Add 'header_in' method which returns value of $COOKIE_HEADER_IN # variable when asked for 'Cookie' header. $request->mock('header_in', sub { my ($self, $name) = @_; return $COOKIE_HEADER_IN if $name eq 'Cookie'; return; }); # Add 'header_out' method which always return fake Apache::Table # object $headers_out. $request->set_always('headers_out', $headers_out); # Add 'add' method which stores passed header value in # $COOKIE_HEADER_OUT variable for 'Set-Cookie' header. $headers_out->mock('add', sub { my ($self, $name, $value) = @_; return unless $name eq 'Set-Cookie'; $COOKIE_HEADER_OUT = $value; }); } # test if we can load module require_ok 'Apache::Perl::Cookie'; { # scenario #1 - no cookie header $COOKIE_HEADER_IN = undef; my %cookies = Apache::Perl::Cookie->fetch; is(scalar(keys %cookies), 0, 'No cookie header - no cookies are expected'); } { # scenario #2 - empty cookie header $COOKIE_HEADER_IN = ''; my %cookies = Apache::Perl::Cookie->fetch; is(scalar(keys %cookies), 0, 'Empty cookie header - no cookies are expected'); } { # scenario #3 - cookie header with one cookie $COOKIE_HEADER_IN = 'Name=Value'; my %cookies = Apache::Perl::Cookie->fetch; is(scalar(keys %cookies), 1, 'One cookie is expected'); is($cookies{Name}->value, 'Value'); } { # scenario #4 - cookie header with two cookies $COOKIE_HEADER_IN = 'Name1=Value1; Name2=Value2'; my %cookies = Apache::Perl::Cookie->fetch; is(scalar(keys %cookies), 2, 'Two cookies are expected'); is($cookies{Name1}->value, 'Value1'); is($cookies{Name2}->value, 'Value2'); } { # scenario #5 - send cookie my $cookie = Apache::Perl::Cookie->new(-name => 'ID', -value => 1234); $cookie->bake; is($COOKIE_HEADER_OUT, 'ID=1234; path=/', 'Test if cookie have been sent correctly'); }

Conclusion

Fake interfaces is great technique which simplifies task of creation controlled environments for automated tests. Once you learned it you have less reasons to avoid writting them :)

Update: Recent versions of Test::MockObject deprecate usage of 'add' method and suggest to use 'mock' method instead of it. Tutorial have been updated to reflect recommended usage.

--
Ilya Martynov (http://martynov.org/)


In reply to Automated software testing: emulation of interfaces using Test::MockObject by IlyaM

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • Outside of code tags, you may need to use entities for some characters:
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others cooling their heels in the Monastery: (9)
    As of 2014-11-23 22:24 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      My preferred Perl binaries come from:














      Results (134 votes), past polls