NAME
    Oktest - a new-style testing library

    ($Release: 0.0100 $)

SYNOPSIS
            use strict;
            use warnings;
            no warnings 'void';   # suppress '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

    *
     Convenient assertion

    *
     Setup/Teardown fixtures

    *
     Unified diff for different texts

    *
     Filtering by string or regular expression

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

    *
     'topic' represents topic or subject of test. Normally, it represents
     ClassName or method_name().

    *
     'spec' represens specification details. You can write description in a
     free text.

    *
     'case_when' represens text context.

    Example (01_basic.t):

            use strict;
            use warnings;
            no warnings 'void';   # suppress '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.
            ## elapsed: 0.000

    Points:

    *
     'topic()' can be nestable. In other words, 'topic()' can contain
     multiple specs and/ore other topics.

    *
     'spec()' can NOT be nestable. You should not put other targes or specs
     in a spec block.

    *
     'case_when()' can contain specs, but cannot contain topic.

    *
     Result is reported by specs, not assertions. For example, a spec
     containing more than two assertions is reported as in a line ('ok' or
     'not ok').

    *
     Oktest calculates number of specs and prints accurate test plan
     automatically. You don't need to update test plan manually, wow!

  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 '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/Teadown
    Oktest provides fixtures (= setup or teardown function).

    *
     'before()' defines setup fixture which is called before each spec.

    *
     'after()' defines teardown fixture which is called after each spec.

    *
     'before_all()' defines setup fixture which is called before all specs.

    *
     'after_all()' defines teardown fixture which is called after all specs.

    Example (04_fixture.t):

            use strict;
            use warnings;
            no warnings 'void';   # suppress '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
            ## 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 '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 simple' or '-ss'):

            $ perl examples/01_basic.t -ss
            ..
            ## elapsed: 0.000

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

            $ perl examples/01_basic.t -ss
            * ClassName
              * method_name(): ..
            ## 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.
            ## elapsed: 0.001

  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

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()'.

    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 'blocks()', 'spec()', or other
     'case_when()'.

    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()'.

    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()'.

    TODO(String description)
     Represents that the test code is not wrote yet.

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

TODO
    *
     [_] Tracer

    *
     [_] Fixture Injection

    *
     [_] Multi-Process Test Runner

AUTHOR
    makoto kuwata <kwa@kuwata-lab.com>

LICENSE
    MIT License

