GrandFather has asked for the wisdom of the Perl Monks concerning the following question:

I'm writing a test suite to check code that is (among other things) parsing an email then using the parse result to generate another email as part of an email driven build system. The generated email will be sent using:

my $msg = MIME::Lite->new (%params); my $result = eval {$msg->send};

How can I check the contents of %params is as I expect from the test code without instrumenting the module under test in some fashion? %params is local to the sub that is setting up for sending the message btw.

I'm using Test::More as the tests framework and for current tests am simply using ok (...).

Update: clean up phrasing that confused bobf and probably others (that's what you get for posting in a hurry before leaving work). :-)


DWIM is Perl's answer to Gödel

Replies are listed 'Best First'.
Re: Checking parameters passed to a third party module during testing
by bobf (Monsignor) on Oct 18, 2006 at 05:38 UTC

    If I understand the description correctly, your code looks like this:

    sub ... { # stuff to parse the email my %params = ... my $msg = MIME::Lite->new (%params); my $result = eval {$msg->send}; }
    and you want to check %params from the test file.

    If that is the case, you could add a validation routine for %params and call it before you create the MIME::Lite object. Then the test file could test the validation routine.

    I'm not completely sure what you mean when you say "check the contents of %params is as I expect without from the test code without instrumenting my module under test", though, so this may not be an option. Also, I assume when you said %params is local to the sub you meant it was lexically scoped to the sub, not declared with local.

      That is in essence correct. In fact params is a compilation of parameters from various places. There is already validation code to check that the correct parameters are present. What I need the test system to do is ensure that the parameters have the correct values given known email text. That is, I want to check that the parsing process is generating the correct output email for a given input email.

      What I think I need in the test script would look something like:

      my $emailBody = '...'; my %generatedParams; # possibly some magic here to associate %generatedParams with %params +contents ok (parseEmail ($emailBody), 'Email parsed without error'); # possibly some magic here to get %params contents into %generatedPara +ms ok ($generatedParams{to} eq 'to@addr', 'To address generated correctly +'); ...

      Yes, I mean %params is lexically scoped to the sub. (That's the C++ shining through.)

      Update: Perhaps I should add that the sub containing the send code is not where the parsing is done,but it is the only place where all the pieces that comprise the email content come together. Various header information such as subject, to and from addresses and possibly other information headers are generated in the sub.


      DWIM is Perl's answer to Gödel
Re: Checking parameters passed to a third party module during testing (wrap)
by tye (Sage) on Oct 18, 2006 at 14:25 UTC

    Put something like this in your test code:

    my $new; BEGIN { require MIME::Lite; $new= MIME::Lite->can( "new" ); undef &MIME::Lite::new; } sub MIME::Lite::new { # validate passed-in arguments here return $new->( @_ ); }

    (Updated to prevent warnings.)

    - tye        

      I thought I had this working earlier today, did something else for a while, then when I came back to it found that the call in the replacement sub is calling itself. The code below demonstrates the problm:

      use strict; use warnings; use Test::More tests => 1; my $new; BEGIN { require MIME::Lite; $new = MIME::Lite->can ("new"); undef &MIME::Lite::new; } sub MIME::Lite::new { print "Validating MIME::Lite::new params\n"; #goto $new; #return $new->(@_); return &$new; } ok (checkSub (), 'Match existing path'); sub checkSub { my $msg = MIME::Lite->new ((To => 'to', )); my $result = eval {$msg->send}; }

      For my current tests it's not essential that the original code be called so I can simply return a success value. However that will not always be appropriate. Any idea what I am doing wrong?

      None of the "return" variants affect the behaviour.

      r
      DWIM is Perl's answer to Gödel
        I'm not 100% sure I can explain what's going on, but the good news is that it's easily fixed. :)

        use strict; use warnings; use Test::More tests => 1; my $new; BEGIN { require MIME::Lite; $new = MIME::Lite->can ("new"); no warnings 'redefine'; *MIME::Lite::new = \&MIME_Lite_new_wrapper; } sub MIME_Lite_new_wrapper { print "Validating MIME::Lite::new params\n"; goto $new; } ok (checkSub (), 'Match existing path'); sub checkSub { my $msg = MIME::Lite->new ((To => 'to', )); my $result = eval {$msg->send}; }

        In your original code, you're redefining MIME::Lite::new(). I'm leaving that function alone, creating a new function, MIME_LITE_new_wrapper(), and then updating the symbol table (*MIME::Lite::new{CODE}) to point to my function. In that way, perl doesn't redefine MIME::Lite::new() to your local sub.

        I don't know enough about the perl guts to understand why the reference to the original in $new isn't sufficient to maintain the original code definition.

        Perhaps someone with more of a clue can chime in. Most of my knowledge of this stuff is cobbled together from perldoc perlref, old posts on c.l.p.m, and looking at the code of various modules that do tricky things with symbol tables.

        Cheers,

      Thank you. That is exactly the sort of solution I was looking for.


      DWIM is Perl's answer to Gödel
          return $new->( @_ };

      Since this is a test module, it may be better to use a goto, rather than just making the call to the old method. If the test module is also testing error conditions that generate messages with any sort of stack trace, then just using return could cause those tests to fail.

        How about some sample code? While I understand tye's sample, I fail to see how I can replace return $new->( @_ }; with a goto to accomplish the same result. How would the following change?

        sub MIME::Lite::new { my ($self, %params) = @_; ok (exists $params{To}, 'To address present'); ok ($params{To} eq 'wibble@wobble', 'To address correct'); ... return $new->( @_ ); }

        DWIM is Perl's answer to Gödel
Re: Checking parameters passed to a third party module during testing
by philcrow (Priest) on Oct 18, 2006 at 11:42 UTC
    I would probably factor out the code that populates %params. That would accomplish to good things. It would remove some clutter from the routine doing the sending and it would make it dead simple to test the refactored code.

    Phil

      Ok, I lied a little. The full code for the sub is:

      sub sendMessage { my (%params) = @_; return if ! CheckParams (\%params, \@okMsgParams, \@requiredMsgPar +ams); $params{Type} = 'text/plain' if ! exists $params{Type}; $params{Date} = Date::EzDate->new ()->{'%Y:%m:%d %T'} if ! exists $params{date}; $params{From} = $BuildManagerContext::gMyEmailAddress if ! exists +$params{From}; my $msg = MIME::Lite->new (%params); my $result = eval {$msg->send}; if (! defined $result) { my $mailMsg = "Error: failed to send message below. Error is $ +@\n"; $mailMsg .= "The following parameters were supplied:\n"; $mailMsg .= "$_: $params{$_}\n" for keys %params; $mailMsg ||= '-- No parameters supplied --'; BuildManagerContext::AddLogString ($mailMsg, 1); BuildManagerContext::FlushLogStrings (); return undef; } return 1; }

      and it gets called from many different places. However it is where all the parts come together and as suggested in the OP it is really only the two lines of the send that are important to the problem. tye's solution gives me exactly the tool that is needed and I can use the same technique in other places to similar effect.


      DWIM is Perl's answer to Gödel