|laziness, impatience, and hubris|
RFC: Test::Refute - extensible unified assertion & testing toolby Dallaylaen (Friar)
|on Jan 05, 2017 at 08:28 UTC||Need Help??|
I would like to present a new assertion/testing tool project. Now there's excellent Test::More with a whole ecosystem built around it and a dozen assertion tools (typically optimizing themselves out in prod environment) available on CPAN. So WHY?
1. It's more or less compatible with Test::More. Actually it aims to be 100% compatible condition-wise and somewhat compatible regarding the test flow, sans black magic like TODO: and SKIP:. (Saner substitutes planned).
2. It has both functional AND object-oriented ("contract") interface. Contracts can be used in production code, say to check user input or plugin module meet requirements. Nesting contracts is possible (that's how subtest is implemented).
3. It's very easy to extend (see below). A Builder is still required, but all it does is wrapping a given condition around for export AND OO-based usage. The internal check logic does NOT depend on it. Writing a custom Contract backend (e.g. outputting XML, or dying after a certain error count) is also possible.
4. Built with testability in mind. Custom tests' failure modes can be easily checked.
5. It's faster. Up to 5x-7x in a synthetic test (see t/speed.t) and up to 30-50% in real life test suite.
I'd rather continue dreaming about the contract feature, if this rant wasn't the last drop.
The test script usage is quite simple (read: ripped off Test::More):
Or the object-oriented interface, runs just happily inside production code (on "oks" on STDOUT, no influence on return code etc):
Or combining best of both worlds:
The whole module is built around a somewhat counter-intuitive, but very simple and powerful interface.$contract->refute( $condition, $explanation );
can be thought of as an assertion turned inside-out. If $condition is false (or null, if we're talking about Java/C++), it is assumed a pass. Everything is fine, no further details needed.
However, if it is true/present, it means that something is wrong, and the condition itself is considered the reason for failure.
This is similar to a program returning zero on successful completion, but different error codes on failure. Or this is like falsifiability concept in modern science. Or, quoting Leo Tolstoy, "All happy families are alike; each unhappy family is unhappy in its own way".
Given the above, ALL your test procedure should care about is trying hard to find a discrepancy in its input (aka pure function).
Now since we need to keep OO/functional interface parity, there IS a Moose-ish builder module that works as follows:
And this is it! A real is() is a tad harder for under handling & output escaping though. More examples can be found in Test::Refute::Basic.
More primitives (talking to the current contract, init/destroy temp stuff etc) are to follow.
Testing your shiny new test is also trivial, no hacks, forks & pipes required.
The project is in deep alpha stage right now, but I hope it evolves into something usable. Looking forward to your support, critique, suggestions, and prior art links.https://github.com/dallaylaen/perl-test-refute