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

Test::Class and test organization

by dragonchild (Archbishop)
on Mar 21, 2006 at 14:17 UTC ( [id://538194]=perlquestion: print w/replies, xml ) Need Help??

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

I'm looking to convert DBM::Deep's test suite to use Test::Class because I need to reuse a large number of test cases. I really don't want to use a library of functions because that's going to get unwieldy very very quickly. So, I was hoping Test::Class would be the solution.

The problem is that I have classes that have waaaay more functionality than can be tested in one file. So, the recommended organisation of Foo::Bar::Test testing Foo::Bar isn't going to work.

What I'm thinking of is writing test classes that encapsulate tests given a certain setup. So, something like:

package Test::Floober; # This inherits from Test::Class use base 'My::Test::BaseClass'; sub test1 : Test(5) { my $self = shift; # Do stuff here with $self->{foo} and $self->{bar} # that were passed in at new() }
Then, in my test file, I could have something like:
use Test::Floober; my $test = Test::Floober->new( foo => 2, bar => 5 ); my $test2 = Test::Floober->new( foo => 'abcd', bar => [ 2 .. 5 ] ); Test::Class->runtests( $test, $test2 );
I got a couple questions:
  • How does the plan get set in these instances? I can run planless, but I'd prefer not to.
  • I'm not sure it will make sense in every instance to pre-build all the test cases as in the situation above. How would Test::Class know the plan then?
  • Is this a sane use of Test::Class? My only exposure to xUnit has been Rails and the tests I wrote there were variations of the stock test, not anything complex.

My criteria for good software:
  1. Does it work?
  2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

Replies are listed 'Best First'.
Re: Test::Class and test organization
by Herkum (Parson) on Mar 21, 2006 at 17:46 UTC

    Your tests are set up here

    sub test1 : Test(5) {
        my $self = shift;
        # Do stuff here with $self->{foo} and $self->{bar}
        # that were passed in at new()
    }
    

    Everytime you call test1 Test::Class knows to add 5 to the expected result.

    The problem I see with your structure here,

    my $test = Test::Floober->new( foo => 2, bar => 5 );
    my $test2 = Test::Floober->new( foo => 'abcd', bar =>  2 .. 5  );
    
    Test::Class->runtests( $test, $test2 );
    
    is that you are trying to call specific tests and ask it to run them. You should write all your tests as methods in your module and then run them all.

    For example, in your test module

    sub new_scalar_values : Test(5) {
        my $self = shift;
        
        my $floober = Floober->new( foo => 2, bar => 5 );
        isa_ok($floober, 'Floober');
        # Create Custom checks here for foo and bar  
    }
    
    sub new_array_references_values : Test(5) {
        my $self = shift;
        
        my $floober = Floober->new( foo => 2, bar => 1..5 );
        isa_ok($floober, 'Floober');
        # Create Custom checks here for foo and bar
       
    }
    

    When you run your tests with Test::Class it will call ALL of your tests, no more managing scripts!

    I recommend the Perl Testing: A Developers Handbook for really learning alot about writing tests

      I may have been unclear. I think I'm trying to create a 2-D grid of tests. I have a series of tests that need to be run every time I have a hash, just to verify that the hash is working correctly. (DBM::Deep is a tied class.) I will want to pass in different control data, but run the same tests using that control data. So, sometimes the control data will be 3 key/value pairs. Sometimes, it will be 4000 k/v pairs. But, the actual tests are the same.

      How would you recommend I organize that?


      My criteria for good software:
      1. Does it work?
      2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

        Could you share some examples of the other dimension in your grid? What I think you're saying is that you have a set of input data that you want to use repetitively in different tests -- but I'm not clear on whether these different tests are classes or subsets of functionality of the classes.

        I'm not sure this necessarily calls for Test::Class. What about factoring all the test cases into a helper module:

        # t/Data.pm package t::Data; our @cases = ( { label => "3 pairs", input => ( 'a' .. 'f' ), }, # etc... ); 1;

        Create all the separate .t files using that package for input data:

        # t/42_somefunctionality.t use Test::More; # no plan here! use t::Data; my $tests_per_case = 13; plan tests => $test_per_case * @t::Data::cases; for my $case ( @t::Data::cases ) { # 13 tests here for each case }

        In the Test::Class paradigm, I think this would done with a superclass and the individual test classes would inherit from it:

        # t/DBM/Deep/Test.pm package t::DBM::Deep::Test; use base 'Test::Class'; use Test::More; my @cases = ( # all test data here ); sub startup :Test(startup) { my $self = shift; $self->{cases} = \@cases; } 1;
        # t/DBM/Deep/Feature1.pm use base 't::DBM::Deep::Test'; use Test::More; sub a_simple_test :Tests { my $self = shift; for my $c ( @{ $self->{cases} } ) { # tests here } } 1;
        # runtests.t use t::DBM::Deep::Feature1; use t::DBM::Deep::Feature2; # etc... Test::Class->runtests();

        This is similar to how something like CGI::Application recommends using an application-wide superclass to provide the common DBI connection and security but individual subclasses for different parts of the application. I'm not sure exactly how to get the plan right, though.

        For another approach (similar to the first one I mentioned), you might want to look at how I structured the tests for Pod::WikiDoc. I put all the test cases as individual files in subdirectories, and then my *.t files called on some fixture code to run a callback function on each test case in a directory. (This is almost exactly what Test::Base is designed to do, but I wanted to avoid that dependency.)

        Is any of this helpful for what you're trying to do?

        -xdg

        Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      The problem I see with your structure here ... is that you are trying to call specific tests and ask it to run them. You should write all your tests as methods in your module and then run them all.

      Nothing wrong with it :-) T::C was explicitly designed to make object-based setting of fixtures possible where class-based stuff was too inflexible.

Re: Test::Class and test organization
by adrianh (Chancellor) on Mar 22, 2006 at 11:12 UTC
    The problem is that I have classes that have waaaay more functionality than can be tested in one file. So, the recommended organisation of Foo::Bar::Test testing Foo::Bar isn't going to work.

    Yes. I need to explain that better in the documentation. For small simple classes it works well, but if you have multiple fixtures multiple classes make excellent sense.

    How does the plan get set in these instances? I can run planless, but I'd prefer not to.

    It should "just work". runtests() will tot up the number of expected tests in both objects and output an appropriate plan. The key is to just call runtests() the once.

    Also Test::Class explicitly checks the number of tests executed for each test method - so even without a plan you'll get a test failure generated by Test::Class if you run to many / few tests. Since this is happening at the method level this is actually safer than a global plan :-)

    Is this a sane use of Test::Class? My only exposure to xUnit has been Rails and the tests I wrote there were variations of the stock test, not anything complex.

    What I'd do normally is have a separate subclass for each fixture. So in Test::Class I'd do something like:

    { package Test::Floober; use base 'Test::Class'; use Test::More; sub setup_fixture : Test( setup ) { my $self = shift; @$self{'foo', 'bar'} = ( $self->foo, $self->bar ); } sub foo_and_bar_exist : Test(2) { my $self = shift; my ($foo, $bar) = @$self{'foo', 'bar'}; ok( $foo, "foo is $foo"); ok( $bar, "bar is $bar"); } __PACKAGE__->SKIP_CLASS( 1 ); # prevent abstract class running } { package Test::Floober::FirstFixture; use base 'Test::Floober'; sub foo { 2 }; sub bar { 5 }; } { package Test::Floober::SecondFixture; use base 'Test::Floober'; sub foo { 'abcd' }; sub bar { [ 2..5 ] }; } Test::Class->runtests;

    Not that there is anything wrong with your way TIMTOWTDI and all that :-)

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others studying the Monastery: (4)
As of 2024-03-29 00:47 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found