Hello dear esteemed monks,
More than once I felt an urge to insert a set of Test::More's checks into my application code, for instance when loading a plug-in or validating a complex piece of data. However, Test::More/Test::Builder is best suited to only run inside test scripts.
So I came up with a module to fill the gap. And I'm going to release it to CPAN soon, unless some huge problem is detected.
The idea is as follows:
What usage I can see this far:
Some details (this is basically a copy-and-paste of the project's README):
use strict; use warnings; use Test::Contract; my $c = contract { $_[0]->like( $user_input, qr/.../, "Format as expected" ); $_[0]->isa_ok( $some_object, "Some::Class" ); }; if ($c->get_passing) { # so far, so good - move on! } else { croak "Contract failed: ".$c->get_tap; };
Of course, Test::Contract can be instantiated just fine if one needs more fine-grained control.
As said above, the most basic check in Test::Contract is $contract->refute( $what_went_unexpected, $why_we_care_about_it );. This may be viewed as an inverted ok or assert:
sub refute { my ($condition, $message) = @_; ok (!$condition, $message) or diag $condition; };
So all one needs to build a new check is to create a function that returns false when its arguments are fine, and an explanation of failure when they are not. Think pure function, although it may have side effects, e.g. checking that a file exists.
A Test::Contract::Engine::Build module exists to simplify the task further:
package My::Check; use Exporter qw(import); use Test::Contract::Engine::Build; build_refute my_check => sub { my ($got, $expected) = @_; # ... a big and nasty check here }, args => 2, export => 1; 1;
This would create an exported function called my_check in My::Check, as well as a my_check method in Test::Contract itself. So the following code is going to be correct:
use Test::More tests => 1; use My::Check; my_check $foo, $bar, "foo is fine";
And this one, too:
# inside a running application use Test::Contract; use My::Check(); # don't pollute global namespace my $c = Test::Contract->new; $c->my_check( $foo, $bar, "runtime-generated foo is fine, too" ); if (!$c->get_passing) { # ouch, something went wrong with $foo and $bar };
It is also possible to validate the testing module itself, outputting details on specifically the tests with unexpected results:
use Test::More; use Test::Contract::Unit qw(contract_is); use My::Check; my $c = contract { my_check $proper_foo, $bar; my_check $good_foo, $bar; my_check $broken_foo, $bar; my_check $good_foo, $wrong_bar; }; is_contract $c, "1100", "my_check works as expected"; done_testing;
Using refutation instead of assertion is similar to the falsifiability concept in modern science.
Or, quoting Leo Tolstoy, "All happy families are alike; each unhappy family is unhappy in its own way".
I would be quite happy if this concept goes outside the Perl community.
As stated above, this module is going to be released to CPAN, but maybe I'm missing something very obvious here...
My previous submission on the topic: RFC: Test::Refute - extensible unified assertion & testing tool.
The project link again: https://github.com/dallaylaen/perl-test-contract.
Thank you, and hope you enjoyed the reading!
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: RFC: Test::Contract - extensible object-oriented runtime check series
by tobyink (Canon) on May 08, 2017 at 01:32 UTC | |
by Dallaylaen (Chaplain) on May 08, 2017 at 09:56 UTC | |
by tobyink (Canon) on May 08, 2017 at 13:27 UTC | |
by Dallaylaen (Chaplain) on May 11, 2017 at 04:46 UTC | |
Re: RFC: Test::Contract - extensible object-oriented runtime check series
by Dallaylaen (Chaplain) on Dec 31, 2017 at 11:19 UTC | |
Re: RFC: Test::Contract - extensible object-oriented runtime check series (DbC)
by Arunbear (Prior) on Dec 26, 2017 at 13:35 UTC | |
by Dallaylaen (Chaplain) on Dec 26, 2017 at 16:49 UTC |