Created
June 14, 2018 09:54
-
-
Save dallaylaen/4ad939aed806153fd05368dcbb2b8b34 to your computer and use it in GitHub Desktop.
Assert::Refute - a unified testing and assertion tool (preview)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Unit tests are great. They show that the code was actually designed | |
to a given spec. | |
[Runtime](https://metacpan.org/pod/PerlX::Assert) [assertions](https://metacpan.org/pod/Devel::Assert) | |
are great. They show that the code is actually running the way it | |
was designed when applied to real data. | |
[Design by contract](https://metacpan.org/pod/Class::Contract) is | |
a great concept, but it's a bit of an overkill for most projects. | |
However, sometimes I feel an urge to just rip several lines from a unit | |
test and put them right into production code. [Test::More](https://metacpan.org/pod/Test::More) | |
doesn't help here much since my application isn't really meant to | |
output `TAP` or run in a harness. | |
So I started out [Assert::Refute](https://metacpan.org/pod/Assert::Refute) | |
to narrow the gap: | |
# somewhere in production code | |
use Assert::Refute qw(:all), { on_fail => 'carp' }; | |
# in the middle of a large sub | |
refute_these { | |
isa_ok $some_object, "My::Type", "Correct type detection | |
after decode_json"; | |
is $fee + $price, $total, "Payment parts match"; | |
like $string, qr/f?o?r?m?a?t?/, "Output is really what we | |
expect it to be"; | |
}; | |
This would just run silently if everything meets the spec, and would | |
emit a warning with TAP included it it doesn't. | |
It is also worth noting that `refute_these { ... }` returns a [report](https://metacpan.org/pod/Assert::Refute::Exec) | |
object (similar to [Test::Builder](https://metacpan.org/pod/Test::Builder), | |
but not a singleton), and that the code block also receives it as | |
first argument. (E.g. one can include more diagnostics if the test | |
is already failing). | |
There is also a `subcontract { ... }` (*not* subtest) call for executing | |
a group of checks together. | |
The rest can be found in [documentation](https://metacpan.org/pod/Assert::Refute). | |
# Why refute? | |
A successful `prove` run actually proves nothing. It's only the failures | |
that are meaningful! This is similar to | |
[falsifiability](https://en.wikipedia.org/wiki/Falsifiability) | |
concept in | |
modern science: we don't prove a theory in experiment; instead, we | |
try hard to refute it. | |
Same goes for runtime assertions. | |
So the underlying mechanism of this module is a *refute* call: | |
$report->refute ("What exactly went wrong", "Why we care about | |
it"); | |
Although somewhat counterintuitive, it leads to more efficient code | |
than a more common | |
ok ("Everything was ok", "Why we care about it") | |
or diag "What exactly went wrong"; | |
# Extending the arsenal | |
The package includes a [builder](https://metacpan.org/pod/Assert::Refute::Build) | |
to create new checks/conditions. Each such condition would be able | |
to work under `Test::More` as well, provided that `Test::More` was | |
loaded earlier than `Assert::Refute`. | |
There is also a small library including tests for arrays, hashes, | |
error messages and warnings. It's currently much more scarce than | |
[Test::Most](https://metacpan.org/pod/Test::Most). | |
There is no easy way to import checks based on `Test::Builder`, but | |
maybe one will be created in the future. | |
# Performance impact | |
I was able to verify 10K `refute_these` blocks with 100 passing assertions | |
each in 3.2 seconds on my 4500 bogomips laptop. `Assert::Refute` is | |
optimized for happy path, for obvious reasons. There's still room | |
for improvement though. | |
# Intended usage | |
The most obvious use case is working on legacy software where | |
`Assert::Refute` may ack as both a prototype of unit tests and | |
a safety net while in transition. | |
Still even in new code it may be feasible to know that certain invariants | |
hold in procution whatever real data is being processed by the code. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment