Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Testing with non-trivial input

by loris (Hermit)
on Nov 25, 2004 at 10:20 UTC ( [id://410366]=perlquestion: print w/replies, xml ) Need Help??

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

Hello all,

I want to write a test for method which is going to extract part of an XML file. The input for this is an array of objects which I generate by parsing a CSV file and creating an object for each line.

My problem is that I am daunted by the setting up the intial conditions for the test. Either I would have to rely on other code (for which, shame on me, I haven't written tests), or generate the objects "by hand". I don't really want to do the later as I need 15 parameters per input object and 4 or 5 objects. Should I be using TestMock::Object, or something similar? I did have a look at some articles about TestMock::Object, but they made my little acolyte brain hurt. OK, no strain, no gain, but I'd like to know whether I'm on the right track first.

Thanks,

loris

Replies are listed 'Best First'.
Re: Testing with non-trivial input
by kappa (Chaplain) on Nov 25, 2004 at 12:16 UTC

    Let me suggest a compromise. Do generate your tests with code. Then check them by hand, using, e.g. a sheet of paper and a pencil and then happily use them to test your production code.

    That means, automate several steps in generating tests. Never test your code with another piece of untested code. I fell into this trap once or twice and it hurts when you suddenly discover that you have just tested that func($input) eq test_func($input) for all inputs while having both these functions identical.

    It's very nice to have the so called "reference implementaion" handy for exactly such cases.

    --kap
Re: Testing with non-trivial input
by fglock (Vicar) on Nov 25, 2004 at 12:21 UTC
      Test::Unit is a complete testing framework -- Perl implementation of the venerable xUnit, while Test::MockObject is a testing utility to emulate complex interfaces not relevant to the code under testing. The latter is very useful whether you use Test::Unit, Test::More or any other framework. They do not address the same problem, sorry. Elephants and motorcycles, kind of.
      --kap
Re: Testing with non-trivial input
by stvn (Monsignor) on Nov 25, 2004 at 16:47 UTC
    Either I would have to rely on other code (for which, shame on me, I haven't written tests), or generate the objects "by hand". I don't really want to do the later as I need 15 parameters per input object and 4 or 5 objects.

    IMO the safer way to do this is to create your 4-5 objects by hand. This way you are sure your test input is correctly formed. I assume these 4-5 objects are instances of already written classes, and the 15 parameters they need are all plain perl data types and not objects themselves. If that is the case then I think you will find the quickest way (and again IMO the safest way) to do this will be to do it by hand. Any other code you write to create these object will need to be tested (more testing time), or using Test::MockObject (more learning time).

    Also I am not sure Test::MockObject is what you are looking for. Test::MockObject can be used to 'fake' an instance of a complex or resource intensive object which would require too much set-up or management code to have for real.

    -stvn
Re: Testing with non-trivial input
by Ytrew (Pilgrim) on Nov 25, 2004 at 17:58 UTC
    Figure out which parts of the function/method you need to think about, and which ones you want to ignore. Then replace the sections you want to ignore with known values. One way to do that is to write a simpler program for each test, which replaces the complex functions with simpler ones for testing purposes.

    What I did was write a little module that let me re-define and restore complex sections of code on the fly. For example, if I wanted to test "spiffy_little_function":

    sub spiffy_little_function { my ($x,$y) = @_; my @params; # parameters to Foo::new my $o; # an object of class Foo my $z; # computed result before taxes my $t; # computed result after taxes @params=get_huge_list_of_parameters($x); $o = Foo->new(@params); $z=$o->compute_result($y); $t = $z * 0.10; return($t) }

    I'd probably do something like this in my test code:

    # test spiffy_little_function with options 'foo' and 'bar' <P> # make test run faster by eliminating slow function call redefine_function("main::get_huge_list_of_params", sub {} ); <P> # use minimal constructor redefine_function("Foo::new",sub { return bless {},"Foo" }); <P> # This method should be called with "bar", and return 42, # for the purposes of this test. redefine_function("Foo::compute_result", sub { 42 } ); <P> $result = spiffy_little_function("foo","bar") is($result,4.2,"spiffy_little_function:foo bar test"); <P> restore_all_functions();

    If I wanted to, and felt the time was worth it, I could write better tests by checking the input paramters in my stub functions, as well. For example, I could have written:

    redefine_function(Foo::compute_result, sub { is($_[0],"bar","spiffy_little_function passed right value to c +ompute_result") return(42); }

    and so forth.

    This way, I don't have to create a lot of different versions of my program, with different stub modules for each test: I can just redefine the complex bits to be simple, on a temporary basis.

    My redefine_function() just manipulates the symbol table entry, and saves the old code reference in a hash: the restore_function() and restore_all_functions restore the original functions again.

    --
    Ytrew

Re: Testing with non-trivial input
by dws (Chancellor) on Nov 25, 2004 at 17:50 UTC

    I want to write a test for method which is going to extract part of an XML file. The input for this is an array of objects which I generate by parsing a CSV file and creating an object for each line.

    I'm not clear on what "the input for this" means. Do you mean that you're providing the method under test with a simulated parse?

    Would something like the following work?

    # ... setup $foo my $xml = <<XML; <?xml version="1.0" encoding="utf-8"?> <stuff> ... </stuff> XML my $out = $foo->method_under_test($xml); ok ( Data::Dumper::Dumper($out), <<EXPECTED ); $VAR1 = { ... }; EXPECTED
    That is, provide the raw XML in-line with the test, and also include the expected Data::Dumper output. Tests like this have the benefit of being fairly transparent, even if setting up $foo means creating several objects.

      Hello dws,

      Nearly a year on and I am again faced with this problem. Back then I missed your reply. What you suggest does seem like a good solution. However, wouldn't I have to worry about whitespace issues when defining the expected XML output?

      Thanks,

      loris

      BTW, "the input for this" meant the information about which fragment of XML to extract was contained in a line of the CSV file.

      "It took Loris ten minutes to eat a satsuma . . . twenty minutes to get from one end of his branch to the other . . . and an hour to scratch his bottom. But Slow Loris didn't care. He had a secret . . ."

        wouldn't I have to worry about whitespace issues when defining the expected XML output?

        Yes, but you only have to worry about it once. In fact, it's good to get a test that fails based on whitespace, since examining the output will give you confidence that the test (once the whitespace is corrected) is testing what you expect.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://410366]
Approved by pelagic
Front-paged by kappa
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (5)
As of 2024-04-19 03:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found