Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Re^2: RFC: Test::Contract - extensible object-oriented runtime check series

by Dallaylaen (Chaplain)
on May 08, 2017 at 09:56 UTC ( [id://1189790]=note: print w/replies, xml ) Need Help??


in reply to Re: RFC: Test::Contract - extensible object-oriented runtime check series
in thread RFC: Test::Contract - extensible object-oriented runtime check series

There are a lot of them; could you please specify the most prominent?

As for the modules I've managed to look into, for instance Carp::Assert, they seem to focus on optimizing themselves away in production.

I think this could be a valuable future option for my module. However, this is not the main point of it.

For now, it's more about providing a way to actually impose limitations on the way code works in both development and production. Optimisation is for later...

Replies are listed 'Best First'.
Re^3: RFC: Test::Contract - extensible object-oriented runtime check series
by tobyink (Canon) on May 08, 2017 at 13:27 UTC

    I guess I'm not really sure what your use case is.

    If your code is mostly object-oriented, you can take advantage of type constraints in any of the major OO frameworks. That automatically covers you for object constructors and accessors.

    Then if you want to check the parameters passed to method calls and function calls, you can re-use the same type constraint checking, courtesy of things like Type::Params. For example, if you want to check that your add function has been passed two numbers:

    use feature qw(state); # Perl 5.10+ use Type::Params qw(compile); use Types::Standard qw(Num); sub add { state $check = compile( Num, Num ); my ($x, $y) = $check->(@_); # will croak if not passed two numbers my $sum = $x + $y; return $sum; }

    If you want to check that certain variables are sane within your function, not just the parameters that are passed as input, then you can still do this with type constraints:

    use feature qw(state); # Perl 5.10+ use Type::Params qw(compile); use Types::Standard qw(Num); sub add { state $check = compile( Num, Num ); my ($x, $y) = $check->(@_); # will croak if not passed two numbers my $sum = $x + $y; Num->assert_return( $sum ); }

    Though frankly, if you keep your function definitions short and simple, this is rarely necessary.

    Then you just come down to a handful of sanity checks that go beyond what type constraints would normally do.

    use feature qw(state); # Perl 5.10+ use Type::Params qw(compile); use Types::Standard qw(ArrayRef); use Carp; use PerlX::Assert; sub zip { state $check = compile( ArrayRef, ArrayRef ); my ($x, $y) = $check->(@_); # An additional check on input. # Carp is a pretty appropriate way of dealing with this. # croak "Arrays must be same length" unless @$x == @$y; my @z = map { [$x->[$i], $y->[$i]] } 0 .. $#$x; # These assertions check the internal logic of our # function, so using Carp makes less sense. It's not # the caller's fault if they fail. Here an assertion # feature makes sense. And because we believe # our logic to be correct, it's hopefully okay to # optimize this away in the production environment. # assert '@z has same length as @$x' { @z == @$x }; assert '@z has same length as @$y' { @z == @$y }; # \@z should be an arrayref of arrayrefs. # ArrayRef->of(ArrayRef)->assert_valid( \@z ); }

    I don't see what the point of adding a big Test::Builder-like framework on top of this would be. What can it cover which the above cannot?

      I'd say type checks as well as normal assertions and unit tests are there to ensure a piece of code works correctly (or, more precisely, fails loudly under known failure modes). This is indeed enough for the majority of cases. A contract on the other hand is there to ensure a different piece of code works correctly (in the same sense). Just like I don't need a contract to make myself do something for myself. However, if other people are involved, agreement on what is to be done is desirable.

      At such a high level, I'm not sure I can produce actual code examples, but I'll try to minimize the hand-waving.

      Ex. 1 A normal croak() would interrupt execution on the first error it sees. Fine for the most part. However, more than once I've seen monstrous, ad-hoc, ill-defined "error collector" classes that are there to ensure all possible errors are reported for a complex input. E.g. all necessary id's are present, they exist in the DB, they actually belong to the current user and allow for the requested operation. Maybe a credit card validation on top of that. Test::Contract could just be a drop-in replacement, but not tied to one project, having an out of the box arsenal of checks, and some concise syntactic sugar.

      Ex. 2 Say we have a web-service that takes a lot of parameters and outputs a JSON. Not quite uncommon these days... Now on the other end we have some poor guy/gal who needs to figure out why their call of our web-service doesn't work. I think it would be nice to let them know all the problems we see with their input and not just one error. This is for test/dev environment, of course, the production will just say "failed to process request" (and log a handy validation report!). UPD: Here!

      Ex. 3 Say we have a unit-test suite written for a Foo::Bar::PP module. Now some other person is willing to make Foo::Bar::XS (or God forbid Foo::Bar::Tiny that utilizes half of CPAN) and claim it's a cleaner, faster drop-in replacement. Of course, they could copy-and-paste the original test suite to make sure it works, or monkey-patch it to use their module instead of the original, or write their own.... Or, using Test::Contract, create a generic test suite that would chew a given module and certify it as a Foo::Bar substitute. UPD: Here!

      Ex. 4 Say we have a framework (ouch) or generally some complex system allowing to load user plug-ins and/or different back-end implementations. Of course, we can just shift the blame to whoever authorized loading those untrusted blobs into our beautiful, carefully designed system. However, providing a micro-test hooked to the loading process seems like a more responsible approach.

      Maybe this is going to make a good start for my example/ directory. And btw thanks for mentioning PerlX::Assert, going to study it now.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://1189790]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (5)
As of 2024-03-28 11:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found