• Stars
    star
    13
  • Rank 1,512,713 (Top 30 %)
  • Language
    Perl
  • Created over 15 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Extendable Perl Testing

Name

Test::Base - A Data Driven Testing Framework

test-base-pm test-base-pm

Synopsis

A new test module:

# lib/MyProject/Test.pm
package MyProject::Test;
use Test::Base -Base;

use MyProject;

package MyProject::Test::Filter;
use Test::Base::Filter -base;

sub my_filter {
    return MyProject->do_something(shift);
}

A sample test:

# t/sample.t
use MyProject::Test;

plan tests => 1 * blocks;

run_is input => 'expected';

sub local_filter {
    s/my/your/;
}

__END__

=== Test one (the name of the test)
--- input my_filter local_filter
my
input
lines
--- expected
expected
output

=== Test two
This is an optional description
of this particular test.
--- input my_filter
other
input
lines
--- expected
other expected
output

Description

Testing is usually the ugly part of Perl module authoring. Perl gives you a standard way to run tests with Test::Harness, and basic testing primitives with Test::More. After that you are pretty much on your own to develop a testing framework and philosophy. Test::More encourages you to make your own framework by subclassing Test::Builder, but that is not trivial.

Test::Base gives you a way to write your own test framework base class that is trivial. In fact it is as simple as two lines:

package MyTestFramework;
use Test::Base -Base;

A module called MyTestFramework.pm containing those two lines, will give all the power of Test::More and all the power of Test::Base to every test file that uses it. As you build up the capabilities of MyTestFramework, your tests will have all of that power as well.

MyTestFramework becomes a place for you to put all of your reusable testing bits. As you write tests, you will see patterns and duplication, and you can "upstream" them into MyTestFramework. Of course, you don't have to subclass Test::Base at all. You can use it directly in many applications, including everywhere you would use Test::More.

Test::Base concentrates on offering reusable data driven patterns, so that you can write tests with a minimum of code. At the heart of all testing you have inputs, processes and expected outputs. Test::Base provides some clean ways for you to express your input and expected output data, so you can spend your

time focusing on that rather than your code scaffolding.

Exported Functions

Test::Base extends Test::More and exports all of its functions. So you can basically write your tests the same as Test::More. Test::Base also exports many functions of its own:

is(actual, expected, [test-name])

This is the equivalent of Test::More's is function with one interesting twist. If your actual and expected results differ and the output is multi- line, this function will show you a unified diff format of output. Consider the benefit when looking for the one character that is different in hundreds of lines of output!

Diff output requires the optional Text::Diff CPAN module. If you don't have this module, the is() function will simply give you normal Test::More output. To disable diffing altogether, set the TEST_SHOW_NO_DIFFS environment variable (or $ENV{TEST_SHOW_NO_DIFFS}) to a true value. You can also call the no_diff function as a shortcut.

blocks( [data-section-name] )

The most important function is blocks. In list context it returns a list of Test::Base::Block objects that are generated from the test specification in the DATA section of your test file. In scalar context it returns the number of objects. This is useful to calculate your Test::More plan.

Each Test::Base::Block object has methods that correspond to the names of that object's data sections. There is also a name and a description method for accessing those parts of the block if they were specified.

The blocks function can take an optional single argument, that indicates to only return the blocks that contain a particular named data section. Otherwise blocks returns all blocks.

my @all_of_my_blocks = blocks;

my @just_the_foo_blocks = blocks('foo');
next_block()

You can use the next_block function to iterate over all the blocks.

while (my $block = next_block) {
    ...
}

It returns undef after all blocks have been iterated over. It can then be called again to reiterate.

first_block()

Returns the first block or undef if there are none. It resets the iterator to the next_block function.

run(&subroutine)

There are many ways to write your tests. You can reference each block individually or you can loop over all the blocks and perform a common operation. The run function does the looping for you, so all you need to do is pass it a code block to execute for each block.

The run function takes a subroutine as an argument, and calls the sub one time for each block in the specification. It passes the current block object to the subroutine.

run {
    my $block = shift;
    is(process($block->foo), $block->bar, $block->name);
};
run_is([data_name1, data_name2])

Many times you simply want to see if two data sections are equivalent in every block, probably after having been run through one or more filters. With the run_is function, you can just pass the names of any two data sections that exist in every block, and it will loop over every block comparing the two sections.

run_is 'foo', 'bar';

If no data sections are given run_is will try to detect them automatically.

NOTE: Test::Base will silently ignore any blocks that don't contain both sections.

is_deep($data1, $data2, $test_name)

Like Test::More's is_deeply but uses the more correct Test::Deep module.

run_is_deeply([data_name1, data_name2])

Like run_is_deeply but uses is_deep which uses the more correct Test::Deep.

run_is_deeply([data_name1, data_name2])

Like run_is but uses is_deeply for complex data structure comparison.

run_is_deeply([data_name1, data_name2])

Like run_is_deeply but uses is_deep which uses the more correct Test::Deep.

run_like([data_name, regexp | data_name]);

The run_like function is similar to run_is except the second argument is a regular expression. The regexp can either be a qr{} object or a data section that has been filtered into a regular expression.

run_like 'foo', qr{<html.*};
run_like 'foo', 'match';
run_unlike([data_name, regexp | data_name]);

The run_unlike function is similar to run_like, except the opposite.

run_unlike 'foo', qr{<html.*};
run_unlike 'foo', 'no_match';
run_compare(data_name1, data_name2)

The run_compare function is like the run_is, run_is_deeply and the run_like functions all rolled into one. It loops over each relevant block and determines what type of comparison to do.

NOTE: If you do not specify either a plan, or run any tests, the run_compare function will automatically be run.

delimiters($block_delimiter, $data_delimiter)

Override the default delimiters of === and ---.

spec_file($file_name)

By default, Test::Base reads its input from the DATA section. This function tells it to get the spec from a file instead.

spec_string($test_data)

By default, Test::Base reads its input from the DATA section. This function tells it to get the spec from a string that has been prepared somehow.

filters( @filters_list or $filters_hashref )

Specify a list of additional filters to be applied to all blocks. See FILTERS below.

You can also specify a hash ref that maps data section names to an array ref of filters for that data type.

filters {
    xxx => [qw(chomp lines)],
    yyy => ['yaml'],
    zzz => 'eval',
};

If a filters list has only one element, the array ref is optional.

filters_delay( [1 | 0] );

By default Test::Base::Block objects are have all their filters run ahead of time. There are testing situations in which it is advantageous to delay the filtering. Calling this function with no arguments or a true value, causes the filtering to be delayed.

use Test::Base;
filters_delay;
plan tests => 1 * blocks;
for my $block (blocks) {
    ...
    $block->run_filters;
    ok($block->is_filtered);
    ...
}

In the code above, the filters are called manually, using the run_filters method of Test::Base::Block. In functions like run_is, where the tests are run automatically, filtering is delayed until right before the test.

filter_arguments()

Return the arguments after the equals sign on a filter.

sub my_filter {
    my $args = filter_arguments;
    # is($args, 'whazzup');
    ...
}

__DATA__
=== A test
--- data my_filter=whazzup
tie_output()

You can capture STDOUT and STDERR for operations with this function:

my $out = '';
tie_output(*STDOUT, $out);
print "Hey!\n";
print "Che!\n";
untie *STDOUT;
is($out, "Hey!\nChe!\n");
no_diff()

Turn off diff support for is() in a test file.

default_object()

Returns the default Test::Base object. This is useful if you feel the need to do an OO operation in otherwise functional test code. See OO below.

WWW() XXX() YYY() ZZZ()

These debugging functions are exported from the Spiffy.pm module. See Spiffy for more info.

croak() carp() cluck() confess()

You can use the functions from the Carp module without needing to import them. Test::Base does it for you by default.

Test Specification

Test::Base allows you to specify your test data in an external file, the DATA section of your program or from a scalar variable containing all the text input.

A test specification is a series of text lines. Each test (or block) is separated by a line containing the block delimiter and an optional test name. Each block is further subdivided into named sections with a line containing the data delimiter and the data section name. A description of the test can go on lines after the block delimiter but before the first data section.

Here is the basic layout of a specification:

=== <block name 1>
<optional block description lines>
--- <data section name 1> <filter-1> <filter-2> <filter-n>
<test data lines>
--- <data section name 2> <filter-1> <filter-2> <filter-n>
<test data lines>
--- <data section name n> <filter-1> <filter-2> <filter-n>
<test data lines>

=== <block name 2>
<optional block description lines>
--- <data section name 1> <filter-1> <filter-2> <filter-n>
<test data lines>
--- <data section name 2> <filter-1> <filter-2> <filter-n>
<test data lines>
--- <data section name n> <filter-1> <filter-2> <filter-n>
<test data lines>

Here is a code example:

use Test::Base;

delimiters qw(### :::);

# test code here

__END__

### Test One
We want to see if foo and bar
are really the same...
::: foo
a foo line
another foo line

::: bar
a bar line
another bar line

### Test Two

::: foo
some foo line
some other foo line

::: bar
some bar line
some other bar line

::: baz
some baz line
some other baz line

This example specifies two blocks. They both have foo and bar data sections. The second block has a baz component. The block delimiter is ### and the data delimiter is :::.

The default block delimiter is === and the default data delimiter is --- .

There are some special data section names used for control purposes:

--- SKIP
--- ONLY
--- LAST

A block with a SKIP section causes that test to be ignored. This is useful to disable a test temporarily.

A block with an ONLY section causes only that block to be used. This is useful when you are concentrating on getting a single test to pass. If there is more than one block with ONLY, the first one will be chosen.

Because ONLY is very useful for debugging and sometimes you forgot to remove the ONLY flag before committing to the VCS or uploading to CPAN, Test::Base by default gives you a diag message saying I found ONLY ... maybe you're debugging?. If you don't like it, use no_diag_on_only.

A block with a LAST section makes that block the last one in the specification. All following blocks will be ignored.

Filters

The real power in writing tests with Test::Base comes from its filtering capabilities. Test::Base comes with an ever growing set of useful generic filters than you can sequence and apply to various test blocks. That means you can specify the block serialization in the most readable format you can find, and let the filters translate it into what you really need for a test. It is easy to write your own filters as well.

Test::Base allows you to specify a list of filters to each data section of each block. The default filters are norm and trim. These filters will be applied (in order) to the data after it has been parsed from the specification and before it is set into its Test::Base::Block object.

You can add to the default filter list with the filters function. You can specify additional filters to a specific block by listing them after the section name on a data section delimiter line.

Example:

use Test::Base;

filters qw(foo bar);
filters { perl => 'strict' };

sub upper { uc(shift) }

__END__

=== Test one
--- foo trim chomp upper
...

--- bar -norm
...

--- perl eval dumper
my @foo = map {
    - $_;
} 1..10;
\ @foo;

Putting a - before a filter on a delimiter line, disables that filter.

Scalar vs List

Each filter can take either a scalar or a list as input, and will return either a scalar or a list. Since filters are chained together, it is important to learn which filters expect which kind of input and return which kind of output.

For example, consider the following filter list:

norm trim lines chomp array dumper eval

The data always starts out as a single scalar string. norm takes a scalar and returns a scalar. trim takes a list and returns a list, but a scalar is a valid list. lines takes a scalar and returns a list. chomp takes a list and returns a list. array takes a list and returns a scalar (an anonymous array reference containing the list elements). dumper takes a list and returns a scalar. eval takes a scalar and creates a list.

A list of exactly one element works fine as input to a filter requiring a scalar, but any other list will cause an exception. A scalar in list context is considered a list of one element.

Data accessor methods for blocks will return a list of values when used in list context, and the first element of the list in scalar context. This is usually "the right thing", but be aware.

The Stock Filters

Test::Base comes with large set of stock filters. They are in the Test::Base::Filter module. See Test::Base::Filter for a listing and description of these filters.

Rolling Your Own Filters

Creating filter extensions is very simple. You can either write a function in the main namespace, or a method in the Test::Base::Filter namespace or a subclass of it. In either case the text and any extra arguments are passed in and you return whatever you want the new value to be.

Here is a self explanatory example:

use Test::Base;

filters 'foo', 'bar=xyz';

sub foo {
    transform(shift);
}

sub Test::Base::Filter::bar {
    my $self = shift;       # The Test::Base::Filter object
    my $data = shift;
    my $args = $self->current_arguments;
    my $current_block_object = $self->block;
    # transform $data in a barish manner
    return $data;
}

If you use the method interface for a filter, you can access the block internals by calling the block method on the filter object.

Normally you'll probably just use the functional interface, although all the builtin filters are methods.

Note that filters defined in the main namespace can look like:

sub filter9 {
    s/foo/bar/;
}

since Test::Base automatically munges the input string into $_ variable and checks the return value of the function to see if it looks like a number. If you must define a filter that returns just a single number, do it in a different namespace as a method. These filters don't allow the simplistic $_ munging.

OO

Test::Base has a nice functional interface for simple usage. Under the hood everything is object oriented. A default Test::Base object is created and all the functions are really just method calls on it.

This means if you need to get fancy, you can use all the object oriented stuff too. Just create new Test::Base objects and use the functions as methods.

use Test::Base;
my $blocks1 = Test::Base->new;
my $blocks2 = Test::Base->new;

$blocks1->delimiters(qw(!!! @@@))->spec_file('test1.txt');
$blocks2->delimiters(qw(### $$$))->spec_string($test_data);

plan tests => $blocks1->blocks + $blocks2->blocks;

# ... etc

The Test::Base::Block Class

In Test::Base, blocks are exposed as Test::Base::Block objects. This section lists the methods that can be called on a Test::Base::Block object. Of course, each data section name is also available as a method.

name()

This is the optional short description of a block, that is specified on the block separator line.

description()

This is an optional long description of the block. It is the text taken from between the block separator and the first data section.

seq_num()

Returns a sequence number for this block. Sequence numbers begin with 1.

blocks_object()

Returns the Test::Base object that owns this block.

run_filters()

Run the filters on the data sections of the blocks. You don't need to use this method unless you also used the filters_delay function.

is_filtered()

Returns true if filters have already been run for this block.

original_values()

Returns a hash of the original, unfiltered values of each data section.

Subclassing

One of the nicest things about Test::Base is that it is easy to subclass. This is very important, because in your personal project, you will likely want to extend Test::Base with your own filters and other reusable pieces of your test framework.

Here is an example of a subclass:

package MyTestStuff;
use Test::Base -Base;

our @EXPORT = qw(some_func);

sub some_func {
    (my ($self), @_) = find_my_self(@_);
    ...
}

package MyTestStuff::Block;
use base 'Test::Base::Block';

sub desc {
    $self->description(@_);
}

package MyTestStuff::Filter;
use base 'Test::Base::Filter';

sub upper {
    $self->assert_scalar(@_);
    uc(shift);
}

Note that you don't have to re-Export all the functions from Test::Base. That happens automatically, due to the powers of Spiffy.

The first line in some_func allows it to be called as either a function or a method in the test code.

Distribution Support

You might be thinking that you do not want to use Test::Base in you modules, because it adds an installation dependency. Fear not. Module::Install::TestBase takes care of that.

Just write a Makefile.PL that looks something like this:

use inc::Module::Install;

name            'Foo';
all_from        'lib/Foo.pm';

use_test_base;

WriteAll;

The line with use_test_base will automatically bundle all the code the user needs to run Test::Base based tests.

Other Cool Features

Test::Base automatically adds:

use strict;
use warnings;

to all of your test scripts and Test::Base subclasses. A Spiffy feature indeed.

History

This module started its life with the horrible and ridicule inducing name Test::Chunks. It was renamed to Test::Base with the hope that it would be seen for the very useful module that it has become. If you are switching from Test::Chunks to Test::Base, simply substitute the concept and usage of chunks to blocks.

Author

Ingy dΓΆt Net <[email protected]>

Copyright

Copyright 2005-2018. Ingy dΓΆt Net.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

See http://www.perl.com/perl/misc/Artistic.html

More Repositories

1

git-subrepo

Shell
3,199
star
2

git-hub

Do GitHub operations from the `git` command
Shell
794
star
3

vroom-pm

Vim Based Slideshow Presentations
Perl
110
star
4

...

Dot Dot Dot
Perl
109
star
5

json-bash

Load, Dump and Manipulate JSON in Bash
Shell
107
star
6

jemplate

Industrial strength JavaScript template framework
Perl
64
star
7

yaml-vim

YAML Highlight script for VIM editor
Vim Script
50
star
8

mo-pm

Perl Micro Objects
Perl
41
star
9

io-all-pm

All in One Perl IO
Perl
38
star
10

bashplus

Modern Bash Programming Framework
Shell
36
star
11

yaml-libyaml-pm

Perl Binding to libyaml
C
33
star
12

pairup

Shell
29
star
13

yaml-pm

YAML Perl Module
Perl
20
star
14

dotdotdot

Organize and share your *nix dot files
Vim Script
19
star
15

pquery-pm

Perl Port of jQuery
Perl
19
star
16

cdent-py

C'Dent - Portable Module Programming Language
Python
19
star
17

inline-pm

Write Perl subroutines in other programming languages
Perl
19
star
18

xxx-pm

See Your Data in the Nude
Makefile
16
star
19

swim-pm

Perl
15
star
20

test-more-bash

Shell
15
star
21

rosettacode-pm

RosettaCode Data Extractor
Perl
12
star
22

lola

Shell
12
star
23

yaml-perl-pm

Pure Perl Port of PyYAML
Python
11
star
24

ajaxterm

Fork of antony.lesuisse.org/ajaxterm
Python
10
star
25

inline-c-pm

Perl
10
star
26

mousse-pm

A Light and Tasty Moose for CPAN Authors
Perl
10
star
27

testml-pm6

TestML for Perl 6
Perl 6
10
star
28

testml-pm

TestML for Perl
Perl
9
star
29

testml

TestML Specification and Documentation
9
star
30

git-xs-pm

Perl XS Wrapper of libgit2
C
8
star
31

package-py

Common parts for Python packages
Python
8
star
32

scim-query-filter-parser-rb

Ruby
8
star
33

stump-pm

Larry Wall's Stump Speech Slideshow Software
Perl
7
star
34

test-base-js

Test.Base module for JavaScript
7
star
35

boolean-pm

Boolean Type Support for Perl
Perl
7
star
36

live-demo

Shell
7
star
37

this-shit

Fork this-shit
7
star
38

testml-tml

TestML Tests for TestML
6
star
39

path-manip-sh

Bash PATH manipulation functions
Shell
6
star
40

yaml-book-2010

YAML - The Book
Ruby
6
star
41

pairup-stackato

PairUp! - The Pair Programming Station that runs as a Stackato App
Shell
6
star
42

only-pm

Load specific Perl module versions
Perl
6
star
43

project-site

Static Content Website Generator
Shell
6
star
44

template-toolkit-simple-pm

A Simple Interface to Template Toolkit
Perl
6
star
45

class-js

Extremely lightweight, but useful JavaScript class wrapper
Perl
6
star
46

yaml-shell-pm

A YAML/Perl Interactive Shell
Perl
6
star
47

jsony-pm

JSONY - Relaxed JSON with a little bit of YAML
Raku
6
star
48

pst

Shell
5
star
49

yaml3-pm

YAML Implementation for Perl 5 using Pegex
Perl
5
star
50

ingydotnet-resume

Ingy dot Net's resume
HTML
5
star
51

perfect-demo

Shell
5
star
52

perl5-pm

Framework module for bundling Perl5 modules
Perl
5
star
53

crockford-py

Crockford base32 encode module for Python
Python
5
star
54

app-aycabtu-pm

All Your Codez Are Belong To Us
Perl
5
star
55

zilla-dist-pm

Perl
5
star
56

ingy-dots

Personal Dot Files
Shell
5
star
57

stardoc-pm

Acmeist Documentation
Perl
5
star
58

cog-osdctw2011-talk

Cog Talk for OSDC::TW 2011
5
star
59

yaml-js

YAML for JavaScript
4
star
60

acmeism-osdctw2010-talk

Please Check Your Guns at the Door -- C'Dent, the Acmeism and Everyone
4
star
61

kwiki

Kwiki Wiki Framework
Perl
4
star
62

yamltime-pm

YAML based Time Tracker App
Perl
4
star
63

yadda

Perl
4
star
64

plack-middleware-cache-pm

Response Caching Middleware for Perl's Plack
Perl
4
star
65

file-share-pm

Improved File::ShareDir for Perl
Makefile
4
star
66

lexicals-pm

Create a hash of your 'my' variables
Perl
4
star
67

moos-pm

Moo Simple
Perl
4
star
68

wikiwyg-net-site

Wikiwyg Website - Static Content
Smalltalk
4
star
69

boot-dots

... bootstrapping dot files
Shell
4
star
70

language-snusp-pm

SNUSP Programming Language Interpreter
Makefile
4
star
71

wikitext-pm

WikiText.pm
Perl
3
star
72

testml-pgx

TestML Spec Grammar in YAML and JSON
JavaScript
3
star
73

testml-py

TestML for Python
Python
3
star
74

jsync-spec

JSYNC Specification
3
star
75

pegex-crontab-pm

Pegex Crontab Parser
Perl
3
star
76

devel-local-pm

Use development versions of other modules
Perl
3
star
77

spiffy-pm

Spiffy Perl Interface Framework For You
Perl
3
star
78

pyplay-py

Play Around in Python
Python
3
star
79

projects-ingy-net-site

Ingy's Project Site -- Static Content
Smalltalk
3
star
80

pegex-pegex-emitter-perl6regex-pm

Pegex Pegex Emitter for Perl 6 Regexes
Perl
3
star
81

yaml-pgx

Pegex Grammar for YAML
Makefile
3
star
82

yaml-to-json-docker

JavaScript
3
star
83

jsync-pm

JSYNC Module for Perl
Perl
3
star
84

module-manifest-skip-pm

MANIFEST.SKIP Management for Perl
Perl
3
star
85

alt-pm

CPAN Module Documenting the Alt Namespace
Perl
3
star
86

pegex-forth-pm

Perl
3
star
87

www.jemplate.net

JavaScript
3
star
88

xxx-pm6

See Your Perl 6 Data in the Nude
Perl
3
star
89

gloom-pm

Great Little OO Module
Perl
3
star
90

testml-code

TestML Examples
Perl
3
star
91

bpan-bash

Shell
3
star
92

yaml-dev-kit

Perl
3
star
93

test-tap-bash

Shell
3
star
94

pyyaml-mirror

A git mirror of PyYAML
Python
3
star
95

test-more-bash-pm

Shell
3
star
96

libyaml-mirror

A git mirror of libyaml
C
3
star
97

yes

CoffeeScript
2
star
98

acmeism-yapceu-2011-talk

YAPC Europe 2011 Acmeism Talk
2
star
99

pegex-ppw2010-talk

Pegex Talk at Pittsburgh Perl Workshop 2010
2
star
100

linux-setup-sh

Bash setup scripts for new Linux installs
Shell
2
star