Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options

Comment on

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

Delaying initialization until first use is a very common technique. And it is an important technique when it comes to making unit testing easier. Initializing too early is a great source of serious, hard-to-work-around problems when it comes to unit testing. (See Devel::Init for a lot of discussion about why and how to avoid problematic premature initialization.)

The specific example given above is obviously contrived. So let me pick a different example:

package MyConfig; ... our %Config = ( ... ); sub getConfig { my( $key ) = @_; our $Host ||= hostname(); our %HostConfig; return $HostConfig{$key} //= _getConfigForHost( $Host, $key ); }

Clearly the value from getConfig($key) depends on which host it was called on. That is the design. It is not a bug. It would be a huge pain and introduce much room for mistakes all over the place if we required everybody who wanted to get a configuration variable to do their own lookup of what their hostname is and pass that in to the API that gets host-specific configuration information.

We could try for both easy-to-use and "the getConfig() API is not buggy according to moritz" with something like:

sub getConfig { my( $key, $host ) = @_; $host ||= hostname(); our %HostConfig; return $HostConfig{$key,$host} //= _getConfigForHost( $host, $key +); }

But there are a lot of ways that that is worse. Getting the configuration for the wrong host is not something that we want people to be doing in Production code. It is very much something we want people to be doing in test code.

So we use the first approach where getting configuration information for any host is easily possible but only by doing things that all of our programmers know constitute ugly peeking under the covers and so are not things that they would do in Production code, but are things that make sense to do in unit test code.

The point is even more important when we are testing code that gets configuration information somewhere under its covers. I may want to be able to test that code from the perspective of some other host without having to plumb the "optionally pass in a hostname parameter" at every single layer between the entry point that I'm testing and any number of places that might be looking up configuration information.

So I want my test code to be able to do:

$MyConfig::Host = $test_host;

And it is convenient to be able to have a single unit test script that can perform tests pretending to be on any number of other hosts with:

for my $host ( ... ) { $MyConfig::Host = $host; %MyConfig::HostConfig = (); ... }

This is an approach that does make it possible to do stupid things that get you strange results (for example, if you leave off the resetting of %MyConfig::HostConfig above). But the only way to get screwed up in Production code is by doing things that are obviously not appropriate in Production code (in our group, anyway). And the ways to get screwed up in test code are just not very likely.

We could add a bunch of code to try catch such unlikely mistakes, but that code would be in the Production MyConfig package. And more code means more complexity which means more chances for bugs. I don't want code in Production modules that is there only to try to prevent unlikely mistakes that are only possible outside of Production.

It is an official best practice in our group to use "our $Var //= ..." over "state" or even a more complex construct with "my". And that is because such persistent variables are very likely to be things that are convenient to be able to override when writing unit tests (for lots of different reasons). And there are lots of cases of things that make perfect sense to cache just one version of in Production because there is only one valid way to get it in Production while it also makes sense to use different and more than one value for those same things in unit tests.

- tye        

In reply to Re^2: 'state' variables and unit testing (Devel::Init) by tye
in thread 'state' variables and unit testing by vsespb

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!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • 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
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            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?

    What's my password?
    Create A New User
    and all is quiet...

    How do I use this? | Other CB clients
    Other Users?
    Others making s'mores by the fire in the courtyard of the Monastery: (8)
    As of 2018-01-24 10:18 GMT
    Find Nodes?
      Voting Booth?
      How did you see in the new year?

      Results (257 votes). Check out past polls.