Makoto Kuwata > Oktest-0.0103 > Oktest

Download:
Oktest-0.0103.tar.gz

Dependencies

Annotate this POD

View/Report Bugs
Module Version: 0.0103   Source  

NAME ^

Oktest - a new-style testing library

($Release: 0.0103 $)

SYNOPSIS ^

        use strict;
        use warnings;
        no warnings 'void';   # suppress warning 'Useless use of ... in void context'
        use Oktest;

        topic "Example1", sub {

            spec "1 + 1 should be equal to 2.", sub {
                OK (1+1) == 2;
            };

            spec "'x' repeats string.", sub {
                OK ('a' x 3) eq 'aaa';
            };

        };

        Oktest::main() if $0 eq __FILE__;
        1;

DESCRIPTION ^

Oktest is a new-style testing library for Perl.

Features:

Structured Test Code

Oktest allows you to write your test code in structured format.

Example (01_basic.t):

        use strict;
        use warnings;
        no warnings 'void';   # suppress warning 'Useless use of ... in void context'
        use Oktest;

        ## 'topic' represents topic of test (such as ClassName or method_name())
        topic "ClassName", sub {

            ## 'topic' can be nestable
            topic "method_name()", sub {

                ## 'spec' describes details of test
                spec "1 + 1 should be equal to 2.", sub {
                    ## 'OK()' describes assertion.
                    OK (1+1) == 2;
                };

                ## a topic can contain multiple specs.
                spec "'x' repeats string.", sub {
                    ## a spec can contain multiple assertions.
                    OK ('a' x 3) eq 'aaa';
                    OK ('a' x 3)->matches(qr/^a+$/);
                };

                ## 'case_when' represents test context
                case_when "value is an array...", sub {
                    my $val = ["SOS"];
                    spec "contains name", sub { OK ($val->[0]) eq "SOS" };
                };
                case_when "value is a hash...", sub {
                    my $val = {name=>"SOS"};
                    spec "contains name", sub { OK ($val->{name}) eq "SOS" };
                };

            };

        };

        Oktest::main() if $0 eq __FILE__;
        1;

Output:

        $ perl 01_basic.t   # or prove 01_basic.t
        1..2
        ## * ClassName
        ##   * method_name()
        ok 1 - 1 + 1 should be equal to 2.
        ok 2 - 'x' repeats string.
        ## ok:2, failed:0, error:0, skipped:0, todo:0  (elapsed: 0.000)

Points:

If you want to know internal mechanism of Oktest, see "Oktest Internal" section.

Assertions

In Oktest, assertion is represented by 'OK()'. You don't need to use 'ok()', 'is()', 'like()', 'isa_ok()', and so on.

Example (02_assertions.t):

        use strict;
        use warnings;
        no warnings 'void';   # suppress warning 'Useless use of ... in void context'
        use Oktest;

        topic "Assertion Example", sub {

            spec "numeric operators", sub {
                OK (1+1) == 2;
                OK (1+1) != 1;
                OK (1+1) >  1;
                OK (1+1) >= 2;
                OK (1+1) <  3;
                OK (1+1) <= 2;
                OK (1+1)->cmp('==', 2);   # or '!=', '>', and so on
                OK (3.141)->in_delta(3.14, 0.01);
            };

            spec "string operators", sub {
                OK ('aaa') eq 'aaa';
                OK ('aaa') ne 'bbb';
                OK ('aaa') lt 'bbb';
                OK ('aaa') le 'aaa';
                OK ('bbb') gt 'aaa';
                OK ('aaa') ge 'aaa';
                OK ('aaa')->cmp('eq', 'aaa');   # or 'ne', 'lt', and so on
                OK ('aaa')->length(3);
            };

            spec "logical expression", sub {
                OK (1==1)->is_truthy();
                OK (0==1)->is_falsy();
                OK (0)->is_defined();
                OK (undef)->not_defined();
            };

            spec "regular expression", sub {
                OK ('FOO')->matches(qr/^[A-Z]+$/);
                OK ('123')->not_match(qr/^[A-Z]+$/);
            };

            spec "type", sub {
                OK ('s')->is_string();
                OK (123)->is_integer();
                OK (0.1)->is_float();
                OK ([1,2,3])->is_ref('ARRAY');
                OK ({x=>10})->is_ref('HASH');
                OK (sub {1})->is_ref('CODE');
            };

            spec "object", sub {
                my $obj = bless({'x'=>1, 'y'=>2}, 'FooClass');
                OK ($obj)->is_a('FooClass');
                OK ($obj)->not_a('BarClass');
                OK ($obj)->has('x', 1)->has('y', 2);
                OK ($obj)->has('x')->has('y');
                OK ($obj)->can_('isa')->can_('can');
                OK ($obj)->can_not('foo')->can_not('bar');
                my $arr = [1, 2, 3];
                OK ($arr)->length(3);
                my $arr2 = [1, 2, 3];
                OK ($arr)->same($arr);
                OK ($arr)->not_same($arr2);
                OK ($arr)->equals($arr2);   ## (EXPERIMENTAL) similar to 'is_deeply()'
            };

            spec "file system", sub {
                use Cwd qw(getcwd);
                my $file = __FILE__;
                my $pwd  = getcwd();
                OK ($file)->file_exists();
                OK ($pwd )->dir_exists();
                OK ("NotExist.txt")->not_exist();
            };

            spec "exception", sub {
                OK (sub { die "SOS\n"  })->dies("SOS\n");
                OK (sub { die "SOS\n"  })->dies(qr/^SOS$/);
                OK (sub { 1 })->not_die();
                #
                OK (sub { warn "SOS\n" })->warns("SOS\n");
                OK (sub { warn "SOS\n" })->warns(qr/^SOS$/);
                OK (sub { 1 })->not_warn();
            };

            spec "collection", sub {
                OK ([3, 6, 9, 12])->all(sub {$_ % 3 == 0});
                OK ([3, 6, 9, 12])->any(sub {$_ % 4 == 0});
            };

        };

        Oktest::main() if $0 eq __FILE__;
        1;

Assertion methods are chainable.

        ## object is an array reference and it's length is 3.
        OK ([1,2,3])->is_ref('ARRAY')->length(3);
        ## object has 'name' and 'team' attributes.
        OK ($obj)->has('name', "Haruhi")->has('team', "SOS");

Setup/Teardown

Oktest provides fixtures (= setup or teardown function).

Example (04_fixture.t):

        use strict;
        use warnings;
        no warnings 'void';   # suppress warning 'Useless use of ... in void context'
        use Oktest;

        topic "Parent", sub {

            before_all { print "= [Parent] before_all\n" };
            after_all  { print "= [Parent] after_all\n" };
            before     { print "= [Parent] before\n" };
            after      { print "= [Parent] after\n" };

            topic "Child1", sub {
                spec "A1", sub { OK (1+1) == 2 };
                spec "B1", sub { OK (1-1) == 0 };
            };

            topic "Child2", sub {
                before_all { print "  = [Child] before_all\n" };
                after_all  { print "  = [Child] after_all\n" };
                before     { print "  = [Child] before\n" };
                after      { print "  = [Child] after\n" };
                spec "A3", sub { OK (1+1) == 2 };
                spec "B4", sub { OK (1-1) == 0 };
            };

        };

        Oktest::main() if $0 eq __FILE__;
        1;

Output example:

        $ perl 04_fixture.t
        1..4
        ## * Parent
        = [Parent] before_all
        ##   * Child1
        = [Parent] before
        = [Parent] after
        ok 1 - A1
        = [Parent] before
        = [Parent] after
        ok 2 - B1
        ##   * Child2
          = [Child] before_all
        = [Parent] before
          = [Child] before
          = [Child] after
        = [Parent] after
        ok 3 - A3
        = [Parent] before
          = [Child] before
          = [Child] after
        = [Parent] after
        ok 4 - B4
          = [Child] after_all
        = [Parent] after_all
        ## ok:4, failed:0, error:0, skipped:0, todo:0  (elapsed: 0.000)

Context data (= a hash object) is passed to 'before' and 'after' blocks. Of course, you can use outer-closure variables instead of context data.

Example:

        topic "Context Example", sub {

            my $member;
            before {
                $member = "Haruhi";
                my $context = shift;
                $context->{team} = "SOS";
            };

            spec "'before' block can set variable.", sub {
                OK ($member) eq "Haruhi";
            };

            spec "'before' block can set context data.", sub {
                my $context = shift;
                OK ($context)->has('team', "SOS");
            };

        };

Oktest provides 'at_end()' function. It registers closure which will be called at end of spec block.

Example:

        topic "at_end() example" sub {

            spec "create and remove files", sub {
                # create data files
                Oktest::Util::write_file("data1.html", "<div></div>");
                Oktest::Util::write_file("data2.html", "<h1></h1>");
                # register closure which will be called at end of spec
                at_end {
                    Oktest::Util::rm_rf("data*.html");
                };
                #
                # ... do test here ...
                #
            };

        };

Skip and TODO

Example of Skip and TODO:

        topic "Misc", sub {

            ## example of 'skip_when()'
            spec "some cool feature is available", sub {
                my $on_windows = $^O =~ /MSWin/;
                skip_when $on_windows, "Windows not supported";
                OK (`echo Haruhi | md5`) eq 'd7f76bdf93d3f59fba678b204fc4faa1';
            };

            ## example of 'TODO()'
            spec "another cool feature is available", sub {
                TODO "not implemented yet.";
            };

            ## Tips: if spec body is not specified then it is regarded as TODO.
            ## For example, the following line is equivarent to above.
            spec "another cool feature is available";

        };

Test::More Migration

Oktest provides helpers to migrate Test::More script into Oktest.

Migration example (06_migrate.t):

        use strict;
        use warnings;
        no warnings 'void';   # suppress warning 'Useless use of ... in void context'

        use Oktest;
        use Oktest::Migration::TestMore;    # imports migration helpers

        topic "Migration Example", sub {

            spec "helpers", sub {
                ok(1+1 == 2, "test name");
                is(1+1, 2, "test name");
                isnt(1+1, 3, "test name");
                like("SOS", qr/^SOS$/, "test name");
                unlike("SOS", qr/^ZOZ$/, "test name");
                cmp_ok(1+1, '>', 1, "test name");
                is_deeply([1,2,3], [1,2,3], "test name");   ## !! EXPERIMENTAL !!
                my $obj = bless({}, 'Dummy');
                can_ok($obj, 'isa');
                isa_ok($obj, 'Dummy', "test name");
                throws_ok(sub { die("SOS\n") }, "SOS\n", "test name");
                throws_ok(sub { die("SOS\n") }, qr/SOS/, "test name");
                dies_ok(sub { die("SOS\n") }, "test name");
                lives_ok(sub { return 1 }, "test name");
                warning_like(sub { warn("SOS\n") }, qr/SOS/, "test name");
                diag("message");
            };

        };

        Oktest::main() if $0 eq __FILE__;
        1;

Filter by Pattern

You can filter topics or specs by pattern.

        ## filter topics
        $ perl t/foo.t --topic='ClassName'      # by string
        $ perl t/foo.t --topic=/^\w+$/          # by regular expression

        ## filter specs
        $ perl t/foo.t --spec='1+1 should be 2'  # by string
        $ perl t/foo.t --spec=/^.*should.*$/     # by regular expression

Reporting Style

In default, Oktest reports results in TAP style format. You can change it by '--style' or '-s' option.

Plain style ('-s plain' or '-sp'):

        $ perl examples/01_basic.t -sp
        ..
        ## ok:2, failed:0, error:0, skipped:0, todo:0  (elapsed: 0.000)

Simple style ('-s simple' or '-ss'):

        $ perl examples/01_basic.t -ss
        * ClassName
          * method_name(): ..
        ## ok:2, failed:0, error:0, skipped:0, todo:0  (elapsed: 0.000)

Verbose style ('-s verbose' or '-sv'):

        $ perl examples/01_basic.t -sv
        * ClassName
          * method_name()
            - [ok] 1 + 1 should be equal to 2.
            - [ok] 'x' repeats string.
        ## ok:2, failed:0, error:0, skipped:0, todo:0  (elapsed: 0.000)

Command-line Interface

Oktest provides 'oktest.pl' script for command-line interface.

        ## run test scripts
        $ oktest.pl t/foo.t t/bar.t

        ## run test scripts under 't' directory
        $ oktest.pl t

        ## change reporting style
        $ oktest.pl -s plain t       # or -sp
        $ oktest.pl -s simple t      # or -ss
        $ oktest.pl -s verbose t     # or -sv

        ## filter by spec description
        $ oktest.pl --spec='1+1 should be 2' t    # string
        $ oktest.pl --spec='/^.*should.*$/' t     # regexp

        ## filter by topic name
        $ oktest.pl --topic='ClassName' t        # string
        $ oktest.pl --topic='/^\w+$/' t          # regexp

Oktest Internal

Internal of Oktest consist of three stages: (1) create tree of topics, (2) counts number of specs, (3) calls spec blocks.

For example:

        topic "ClassName", sub {
            topic "method_foo()", sub {
                sub "spec1", sub { ... };
                sub "spec2", sub { ... };
            };
            topic "method_bar()", sub {
                sub "spec3", sub { ... };
                sub "spec4", sub { ... };
            };
        };
        Oktest::main();

The above code is equvarent roughly to the follwing:

        ## Step (1): creates tree of topics
        ## (Notice that topic blocks are called but spec blocks are not called yet)
        my $t1 = TopicObject->new("ClassName");
        my $t2 = TopicObject->new("method_foo()", $t1);
        $t2->add_spec(SpecObject->new("spec1", sub { ... }));
        $t2->add_spec(SpecObject->new("spec2", sub { ... }));
        my $t3 = TopicObject->new("method_bar()", $t1);
        $t3->add_spec(SpecObject->new("spec3", sub { ... }));
        $t3->add_spec(SpecObject->new("spec4", sub { ... }));

        ## Step (2): counts number of specs and prints test plan
        my $n = $t1->_count_specs();
        print "1..$n\n";

        ## Step (3): call spec blocks and prints results
        for my $to ($t1->{topics}) {         ## $to is TopicObject
            for my $so ($to->{specs}) {      ## $so is SpecObject
                undef $@;
                eval { $so->{block}->() };
                print $@ ? "not ok - " : "ok - ";
                print $so->{desc}, "\n";
            }
        }

The above shows difference between Oktest and Test::More.

REFERENCE ^

package Oktest

topic(String name, Code block)

Represents spec topic, for example ClassName, method_name(), or feature-name.

Block of 'topic()' can contain other 'topic()', 'case_when()', and 'spec()'.

See "Structured Test Code" section for sample code.

case_when(String description, Code block)

Represents test context, for example "when data is not found in database..." or "when argument is not passed...".

This is almost same as 'topic()', but intended to represent test context.

Block of 'case_when()' can contain 'block()', 'spec()', or other 'case_when()'.

See "Structured Test Code" section for sample code.

spec(String description[, Code block])

Represents spec details, for example "should return integer value" or "should die with appropriate message".

Argument 'description' describes spec description, and 'block' contains assertions to validate your code.

If body block is not passed then 'sub { TODO("not implemented yet") }' is created instead.

Body of 'spec()' can't contain both 'topics()', 'case_when()' nor 'spec()'.

This function should be called in blocks of 'topic()' or 'case_when()'.

See "Structured Test Code" section for sample code.

OK(Any actual)

Represents assertion.

See "Assertions" section for sample code.

If you call OK() but no assertion specified, Oktest will report you about it.

        ## Assertion 'is_a' specified
        OK (Class->new())->is_a('Class');
        ## No assertion specified, and Oktest will report you about it
        OK (Class->new())->isa('Class');   # 'isa' is not an assertion
skip_when(Boolean condition, String reason)

If condition is true then the rest assertions in the same spec are skipped.

This should be called in blocks of 'spec()'.

See "Skip and TODO" section for sample code.

TODO(String description)

Represents that the test code is not wrote yet.

This should be called in blocks of 'spec()'.

See "Skip and TODO" section for sample code.

before(Code block)

Register code block to be called before each spec. If topics are nested then outer 'before' block is called before inner 'before' block.

This is equivarent to setUp() method in xUnit.

See "Setup/Teardown" section for sample code.

after(Code block)

Register code block to be called after each spec. If topics are nested then inner 'after' block is called before outer 'after' block.

This is equivarent to tearDown() method in xUnit.

See "Setup/Teardown" section for sample code.

before_all(Code block)

Register code block to be called before all specs. In other words, this code block is called only once.

See "Setup/Teardown" section for sample code.

after_all(Code block)

Register code block to be called after all specs. In other words, this code block is called only once.

See "Setup/Teardown" section for sample code.

at_exit(Code block)

Register code block to be called after that spec. This is very convenient to specify 'tearDown' operation for a certaion spec.

This should be called in spec block.

See "Setup/Teardown" section for sample code.

Oktest::main()

Runs all specs and reports result.

This should be called as 'Oktest::main()', not 'main()'.

See "Structured Test Code" section for sample code.

package Oktest::Migration::TestMore

See "Test::More Migration" section.

TODO ^

AUTHOR ^

makoto kuwata <kwa@kuwata-lab.com>

LICENSE ^

MIT License

syntax highlighting: