NAME
Assert::Refute - Unified testing and assertion tool
DESCRIPTION
This module adds Test::More-like code snippets called contracts to your production code, without turning the whole application into a giant testing script.
Each contract is compiled once and executed multiple times, generating reports objects that can be queried to be successful or printed out as TAP if needed.
The condition arsenal may be extended, producing functions that will run uniformly both inside contract
blocks and in a unit-testing script.
SYNOPSIS
use Assert::Refute;
my $spec = contract {
my ($foo, $bar) = @_;
is $foo, 42, "Life";
like $bar, qr/b.*a.*r/, "Regex";
};
# later
my $report = $spec->apply( 42, "bard" );
$report->get_count; # 2
$report->is_passing; # true
$report->get_tap; # printable summary *as if* it was Test::More
REFUTATIONS AND CONTRACTS
refute($condition, $message)
stands for an inverted assertion. If $condition is false, it is regarded as a success. If it is true, however, it is considered to be the reason for a failing test.
This is similar to how Unix programs set their exit code, or to Perl's own $@
variable, or to the falsifiability concept in science.
A contract{ ... }
is as block of code containing various assumptions about its input. An execution of such block is considered successful if none of these assumptions were refuted.
A subcontract
is an execution of previously defined contract in scope of the current one, succeeding silently, but failing loudly.
These three primitives can serve as building blocks for arbitrarily complex assertions, tests, and validations.
EXPORT
All of the below functions are exported by default, as well as some basic assumptions mirroring the Test::More suite.
use Assert::Refute qw(:core);
would only export contract
, refute
, contract_is
, subcontract
, and current_contract
functions.
use Assert::Refute qw(:basic);
would export the following testing primitives:
is
, isnt
, ok
, use_ok
, require_ok
, cmp_ok
, like
, unlike
, can_ok
, isa_ok
, new_ok
, contract_is
, subcontract
, is_deeply
, note
, diag
.
See Assert::Refute::T::Basic for more.
Use Assert::Refute::Contract if you insist on no exports and purely object-oriented interface.
contract { ... }
Create a contract specification object for future use. The form is either
my $spec = contract {
my @args = @_;
# ... work on input
refute $condition, $message;
};
or
my $spec = contract {
my ($contract, @args) = @_;
# ... work on input
$contract->refute( $condition, $message );
} need_object => 1;
The need_object
form may be preferable if one doesn't want to pollute the main namespace with test functions (is
, ok
, like
etc) and instead intends to use object-oriented interface.
Other options are TBD.
Note that contract does not validate anything by itself, it just creates a read-only Assert::Refute::Contract object sitting there and waiting for an apply
call.
The apply
call returns a Assert::Refute::Exec object containing results of specific execution.
This is much like prepare
/ execute
works in DBI.
refute( $condition, $message )
Test a condition using the current contract. If no contract is being executed, dies.
The test passes if the $condition is false, and fails otherwise.
subcontract( "Message" => $contract, @arguments )
Execute a previously defined contract and fail loudly if it fails.
[NOTE] that the message comes first, unlike in refute
or other test conditions, and is required.
For instance, one could apply a previously defined validation to a structure member:
my $valid_email = contract {
my $email = shift;
# ... define your checks here
};
my $valid_user = contract {
my $user = shift;
is ref $user, 'HASH'
or die "Bail out - not a hash";
like $user->{id}, qr/^\d+$/, "id is a number";
subcontract "Check e-mail" => $valid_email, $user->{email};
};
# much later
$valid_user->apply( $form_input );
Or pass a definition as argument to be applied to specific structure parts (think higher-order functions, like map
or grep
).
my $array_of_foo = contract {
my ($is_foo, $ref) = @_;
foreach (@$ref) {
subcontract "Element check", $is_foo, $_;
};
};
$array_of_foo->apply( $valid_user, \@user_list );
current_contract
Returns the Assert::Refute::Exec object being worked on. Dies if no contract is being executed at the time.
This is actually a clone of "current_contract" in Assert::Refute::Build.
EXTENDING THE SUITE
Although building wrappers around refute
call is easy enough, specialized tool exists for doing that.
Use Assert::Refute::Build to define new checks as both prototyped exportable functions and their counterpart methods in Assert::Refute::Exec. Such functions will then run just fine in both contract
blocks and usual unit-testing scripts built with Test::More.
Subclass Assert::Refute::Exec to create new drivers, for instance, to register failed/passed tests in your unit-testing framework of choice or generate warnings/exceptions when conditions are not met.
BUGS
This module is still in ALPHA stage.
Test coverage is maintained at >80%, but who knows what lurks in the other 20%.
See https://github.com/dallaylaen/assert-refute-perl/issues to browse old bugs or report new ones.
SUPPORT
You can find documentation for this module with the perldoc
command.
perldoc Assert::Refute
You can also look for information at:
First and foremost, use Github!
RT
: CPAN's request tracker (report bugs here)AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
ACKNOWLEDGEMENTS
LICENSE AND COPYRIGHT
Copyright 2017 Konstantin S. Uvarin. <khedin at gmail.com>
This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:
http://www.perlfoundation.org/artistic_license_2_0
Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.
If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.
This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.
This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.
Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.