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

I'm wondering how fellow monks test in regards to the outputs of functions. By output, I mean longer string output that's generated to appeal visually to the user.

I have a class that does it's thing, you know, holds data, has methods, the usual things a class does - nothing to amazing. The class also has an 'output' method, so $obj->output; will output its information in a human-readable form. I've also subclassed this class for output purposes, ie, Foo::HTML overrides the output method so $obj->output; will output in HTML format, and Foo::XML will output in XML; and if I ever would want to do something like Foo::Tk, the possibility is there. Now, when writing tests for my class, I have written good tests for almost all of my methods so I can be sure they do what they claim, but what about these output methods?

One thought would be to do something like:

is( $obj->output, qq(<div class="Foo">&lt;Relevant Info&gt;</div>) );
But then everytime I want to change my html output slightly, I would have to rewrite my tests to succeed (and it should be the other way around... rewrite the code so the tests succeed).

The way I'm doing it right now (since I'm still trying to figure out what I want to do), is check that the output is defined and not equal to "".

So, what's the best way to check output? Do you decided absolutely what the output will look like at the beginning of the project and hardcode it into a test? Do you just ignore testing output, and look at the output after every change? Do you do something completely different?

I'm eager to hear what most other monks do! Thanks in advance.

Update:
It has come to my attention (thanks thcsoft)that the question may not be clear. Let me think about this for a moment. Must be one of those things where it makes sense in my head what I'm asking, but not to others.

Okay, what I'm getting at is that my class generates up to 10 lines of output, formatted as HTML, based on the current state of the object. It doesn't seem reasonable to hardcode this 10 lines of output into a test, especially if I plan to make minor aesthetic updates later on. So how is longer output like this generally tested? Or is it tested?

I don't know if that makes it any more clear, I hope it does.

    -Bryan

Replies are listed 'Best First'.
Re: use Test; and output
by mpeters (Chaplain) on Jun 14, 2005 at 13:52 UTC
    It all depends on what your module's contract is. If it says it's going to produce some HTML in an exact way, then you need to test that exactly, hardcoding, if necessary. If it just says "will produce valid HTML/XML", etc, then you just need to test that.

    In your case, I suspect the latter and I would use some other modules to check that the output is a valid form of the desired output (maybe Test::XML::Valid?).

    More people are killed every year by pigs than by sharks, which shows you how good we are at evaluating risk. -- Bruce Schneier
Re: use Test; and output
by dragonchild (Archbishop) on Jun 14, 2005 at 14:43 UTC
    mpeters has the right of it. You need to test
    1. that the output() function is getting its info correctly (by testing the functions it calls)
    2. that the output() function is outputting correctly formatted whatever
    3. that the output() function's whatever has the relevant bits in the relevant places.

    Personally, I'd use something like XML::Parser or HTML::Parser vs. modifying my tests every time. Then, you just look for the relevant text nodes and go from there.


    But, that solution sucks rocks. The better solution is to use templates or output decorators. Don't worry about how it's being outputted. Then, the output decorators can be tested for general completeness instead of specific correctness. (Or, even better, you can use a CPAN module and fuhgeddaboudit!)


    • In general, if you think something isn't in Perl, try it out, because it usually is. :-)
    • "What is the sound of Perl? Is it not the sound of a wall that people have stopped banging their heads against?"
Re: use Test; and output
by tphyahoo (Vicar) on Jun 15, 2005 at 08:04 UTC
    I had pretty much this problem, with a program that sucked some html in, made many transformations, and outputted it, on thousands of files. The problem was, how to verify all the edge cases did what I wanted, all of what I wanted, and nothing but what I wanted. What I wound up doing is basically, a test script that looks like
    perl fix_fonts_test.pl C:\thomasdata\thomasprojects\fixhtml\test\fonts +\stripStandard Fixtotext perl fix_fonts_test.pl C:\thomasdata\thomasprojects\fixhtml\test\fonts +\toClassWeiss Fixtoclassweiss perl fix_fonts_test.pl C:\thomasdata\thomasprojects\fixhtml\test\fonts +\toClassRot Fixtoclassrot perl fix_fonts_test.pl C:\thomasdata\thomasprojects\fixhtml\test\fonts +\toClassKlein Fixtoclassklein perl fix_fonts_test.pl C:\thomasdata\thomasprojects\fixhtml\test\fonts +\toClassesKleinNGrau Fixtoclasskleinandgrau
    The first arg is a test target directory, the second arg is a "transformer" object. In the test directory, I had various subdirectories, some of which were just placeholders, while others had a "before.html" and "after.html" file inside. The ones with before and after files were identified as test target directories. The script transformed the "before", and compared compared the rest to the "after." Actually, to get this to work I had to put both before and after into a canonical format by trimming whitespace, other little tidbits, but that's the basic idea. Test script:
    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; use File::Find; #custom modules use Modules::Htmlripper::Noreplaceroot::Fixtotext; use Modules::Htmlripper::Gotreplaceroot::Fixtoclassweiss; use Modules::Htmlripper::Gotreplaceroot::Fixtoclassrot; use Modules::Htmlripper::Gotreplaceroot::Fixtoclassklein; use Modules::Htmlripper::Gotnestedreplaceroot::Fixtoclasskleinandgrau; use Modules::Filecontrol qw(get_files); my $test_dir = shift or die "no test directory specified file\n"; opendir (DIR, $test_dir) or die "couldn't open directory: $test_dir"; closedir DIR; my $ripperClass = shift or die "no HTML ripper class specified"; my @test_dirs = Filecontrol::get_test_dirs($test_dir); my $ripper = $ripperClass->new(); #keys are directory names. for ( @test_dirs ) { print "Testing $ripperClass against directory $_: \n"; $ripper->test_dir($_); my $result = $ripper->test(); if ($result) { print "ok\n"; } else { print "not ok\n"; } }
    Test dir grabber:
    sub get_test_dirs { my $dir = shift or die "no directory specified."; opendir (DIR, $dir) or die "couldn't open directory: $dir"; my @directories_to_search = ("$dir"); my %dirs = (); find( sub { if (-d $File::Find::name) { # it's a dir my $current_dir = $File::Find::name; if ( (-f "$current_dir" . '/before.html') && (-f "$cur +rent_dir" . '/after.html') ) { #before and after files exist $dirs{$current_dir} = 1; } #end check if file } # end check if directory }, @directories_to_search ); my @dirs = keys %dirs; return @dirs; }
    Maybe you can adopt this to your situation. Hope this helps!
Re: use Test; and output
by adrianh (Chancellor) on Jun 16, 2005 at 23:01 UTC
    So, what's the best way to check output? Do you decided absolutely what the output will look like at the beginning of the project and hardcode it into a test? Do you just ignore testing output, and look at the output after every change? Do you do something completely different?

    Some suggestions:

    • Put as little presentational markup in the HTML as possible, relying on CSS for look and feel. This makes the underlying HTML far less likely to change.
    • Test for well formed HTML, since that catches lots of typos.
    • To get something quick and dirty up and running use a regex test, e.g. like( $obj->output, qr/relevant info/i ).

    In general I try and make output layers as thin as possible, do as little as possible, and have as little knowledge about what they're presenting as possible. For example rather than having:

    my $o = FribbleList::HTML->new; $o->output;

    I'd tend to have something like

    my $html_list_output = ListOutput::HTML->new; my $o = FribbleList->new( output => $html_list_output ); $o->output; # really does $html_list_output->output( $o->list );

    so I can test my FribbleList class completely independently of my ListOutput::HTML class.

Re: use Test; and output
by thcsoft (Monk) on Jun 14, 2005 at 13:36 UTC
    you're allowed to call me dumb. but even after i had read your post the third time, i couldn't figure out a possible meaning from it...

    language is a virus from outer space.