如何在 Perl 中实现断言?
How to implement assert in Perl?
当尝试在 Perl 中实现 C 的 assert()
宏时,存在一些基本问题。首先考虑这段代码:
sub assert($$) {
my ($assertion, $failure_msg) = @_;
die $failure_msg unless $assertion;
}
# ...
assert($boolean, $message);
虽然这行得通,但它不像 C:在 C 中我会写 assert($foo <= $bar)
,但使用此实现我必须写 assert($foo <= $bar, '$foo <= $bar')
,即 repeat 条件为字符串。
现在我想知道如何有效地实施这个。 easy 变体似乎将字符串传递给 assert()
并使用 eval
来评估字符串,但是在评估 eval 时您无法访问变量。即使它能工作,它也会非常低效,因为每次都对条件进行解析和评估。
传递表达式时,我不知道如何从中生成一个字符串,尤其是它已经被求值了。
使用 assert(sub { $condition })
的另一种变体可能更容易从代码引用中生成字符串,但被认为太丑陋了。
结构 assert(sub { (eval $_[0], $_[0]) }->("condition"));
和
sub assert($)
{
die "Assertion failed: $_[1]\n" unless $_[0];
}
可以,但很难调用。
我正在寻找的 解决方案 是 编写条件以检查 仅一次 ,同时能够再现原始(未评估的)条件并且有效地评估条件。
那么有哪些比较优雅的方案呢?显然,如果 Perl 有一个宏或类似的语法机制,允许在编译或评估之前转换输入,那么解决方案会更容易。
使用B::Deparse?
#!/usr/bin/perl
use strict;
use warnings;
use B::Deparse;
my $deparser = B::Deparse->new();
sub assert(&) {
my($condfunc) = @_;
my @caller = caller();
unless ($condfunc->()) {
my $src = $deparser->coderef2text($condfunc);
$src =~ s/^\s*use\s.*$//mg;
$src =~ s/^\s+(.+?)//mg;
$src =~ s/(.+?)\s+$//mg;
$src =~ s/[\r\n]+/ /mg;
$src =~ s/^\{\s*(.+?)\s*\}$//g;
$src =~ s/;$//mg;
die "Assertion failed: $src at $caller[1] line $caller[2].\n";
}
}
my $var;
assert { 1 };
#assert { 0 };
assert { defined($var) };
exit 0;
测试输出:
$ perl dummy.pl
Assertion failed: defined $var at dummy.pl line 26.
CPAN 上有大量断言模块。这些都是开源的,因此很容易查看它们并了解它们是如何完成的。
Carp::Assert is a low-magic implementation. It has links to a few more complicated assertion modules in its documentation, one of which is my module PerlX::Assert.
使用 caller
并提取做出断言的源代码行?
sub assert {
my ($condition, $msg) = @_;
return if $condition;
if (!$msg) {
my ($pkg, $file, $line) = caller(0);
open my $fh, "<", $file;
my @lines = <$fh>;
close $fh;
$msg = "$file:$line: " . $lines[$line - 1];
}
die "Assertion failed: $msg";
}
assert(2 + 2 == 5);
输出:
Assertion failed: assert.pl:14: assert(2 + 2 == 5);
如果您使用 Carp::croak
而不是 die
,Perl 还将报告堆栈跟踪信息并确定调用失败断言的位置。
任何一种 "assertions" 的方法是使用测试框架。它不像 C 的 assert
那样干净利落,但它更加灵活和易于管理,同时测试仍然可以像 assert
语句一样自由地嵌入代码中。
几个非常简单的例子
use warnings;
use strict;
use feature 'say';
use Test::More 'no_plan';
Test::More->builder->output('/dev/null');
say "A few examples of tests, scattered around code\n";
like('may be', qr/(?:\w+\s+)?be/, 'regex');
cmp_ok('a', 'eq', 'a ', 'string equality');
my ($x, $y) = (1.7, 13);
cmp_ok($x, '==', $y, '$x == $y');
say "\n'eval' expression in a string so we can see the failing code\n";
my $expr = '$x**2 == $y';
ok(eval $expr, 'Quadratic') || diag explain $expr;
# ok(eval $expr, $expr);
有输出
A few examples of tests, scattered around code
# Failed test 'string equality'
# at assertion.pl line 19.
# got: 'a'
# expected: 'a '
# Failed test '$x == $y'
# at assertion.pl line 20.
# got: 1.7
# expected: 13
'eval' expression in a string so we can see the failing code
# Failed test 'Quadratic'
# at assertion.pl line 26.
# $x**2 == $y
# Looks like you failed 3 tests of 4.
这只是一些例子,最后一个直接回答了问题。
模块 Test::More brings together a number of tools; there are many options in how to use it and how to manipulate output. See Test::Harness, and Test::Builder(上面使用),以及一些教程和 SO 帖子。
我不知道上面的 eval
如何计入 "elegant" 但它确实让你从单一的和单独关心的 C 风格 assert
语句转向更易于管理的系统.
好的断言意味着并计划为系统测试和代码文档,但就其本质而言,它们缺乏正式的结构(因此最终可能仍然是分散的和临时的)。以这种方式完成后,它们会附带一个框架,并且可以作为一个套件使用许多工具进行管理和调整。
当尝试在 Perl 中实现 C 的 assert()
宏时,存在一些基本问题。首先考虑这段代码:
sub assert($$) {
my ($assertion, $failure_msg) = @_;
die $failure_msg unless $assertion;
}
# ...
assert($boolean, $message);
虽然这行得通,但它不像 C:在 C 中我会写 assert($foo <= $bar)
,但使用此实现我必须写 assert($foo <= $bar, '$foo <= $bar')
,即 repeat 条件为字符串。
现在我想知道如何有效地实施这个。 easy 变体似乎将字符串传递给 assert()
并使用 eval
来评估字符串,但是在评估 eval 时您无法访问变量。即使它能工作,它也会非常低效,因为每次都对条件进行解析和评估。
传递表达式时,我不知道如何从中生成一个字符串,尤其是它已经被求值了。
使用 assert(sub { $condition })
的另一种变体可能更容易从代码引用中生成字符串,但被认为太丑陋了。
结构 assert(sub { (eval $_[0], $_[0]) }->("condition"));
和
sub assert($)
{
die "Assertion failed: $_[1]\n" unless $_[0];
}
可以,但很难调用。 我正在寻找的 解决方案 是 编写条件以检查 仅一次 ,同时能够再现原始(未评估的)条件并且有效地评估条件。
那么有哪些比较优雅的方案呢?显然,如果 Perl 有一个宏或类似的语法机制,允许在编译或评估之前转换输入,那么解决方案会更容易。
使用B::Deparse?
#!/usr/bin/perl
use strict;
use warnings;
use B::Deparse;
my $deparser = B::Deparse->new();
sub assert(&) {
my($condfunc) = @_;
my @caller = caller();
unless ($condfunc->()) {
my $src = $deparser->coderef2text($condfunc);
$src =~ s/^\s*use\s.*$//mg;
$src =~ s/^\s+(.+?)//mg;
$src =~ s/(.+?)\s+$//mg;
$src =~ s/[\r\n]+/ /mg;
$src =~ s/^\{\s*(.+?)\s*\}$//g;
$src =~ s/;$//mg;
die "Assertion failed: $src at $caller[1] line $caller[2].\n";
}
}
my $var;
assert { 1 };
#assert { 0 };
assert { defined($var) };
exit 0;
测试输出:
$ perl dummy.pl
Assertion failed: defined $var at dummy.pl line 26.
CPAN 上有大量断言模块。这些都是开源的,因此很容易查看它们并了解它们是如何完成的。
Carp::Assert is a low-magic implementation. It has links to a few more complicated assertion modules in its documentation, one of which is my module PerlX::Assert.
使用 caller
并提取做出断言的源代码行?
sub assert {
my ($condition, $msg) = @_;
return if $condition;
if (!$msg) {
my ($pkg, $file, $line) = caller(0);
open my $fh, "<", $file;
my @lines = <$fh>;
close $fh;
$msg = "$file:$line: " . $lines[$line - 1];
}
die "Assertion failed: $msg";
}
assert(2 + 2 == 5);
输出:
Assertion failed: assert.pl:14: assert(2 + 2 == 5);
如果您使用 Carp::croak
而不是 die
,Perl 还将报告堆栈跟踪信息并确定调用失败断言的位置。
任何一种 "assertions" 的方法是使用测试框架。它不像 C 的 assert
那样干净利落,但它更加灵活和易于管理,同时测试仍然可以像 assert
语句一样自由地嵌入代码中。
几个非常简单的例子
use warnings;
use strict;
use feature 'say';
use Test::More 'no_plan';
Test::More->builder->output('/dev/null');
say "A few examples of tests, scattered around code\n";
like('may be', qr/(?:\w+\s+)?be/, 'regex');
cmp_ok('a', 'eq', 'a ', 'string equality');
my ($x, $y) = (1.7, 13);
cmp_ok($x, '==', $y, '$x == $y');
say "\n'eval' expression in a string so we can see the failing code\n";
my $expr = '$x**2 == $y';
ok(eval $expr, 'Quadratic') || diag explain $expr;
# ok(eval $expr, $expr);
有输出
A few examples of tests, scattered around code # Failed test 'string equality' # at assertion.pl line 19. # got: 'a' # expected: 'a ' # Failed test '$x == $y' # at assertion.pl line 20. # got: 1.7 # expected: 13 'eval' expression in a string so we can see the failing code # Failed test 'Quadratic' # at assertion.pl line 26. # $x**2 == $y # Looks like you failed 3 tests of 4.
这只是一些例子,最后一个直接回答了问题。
模块 Test::More brings together a number of tools; there are many options in how to use it and how to manipulate output. See Test::Harness, and Test::Builder(上面使用),以及一些教程和 SO 帖子。
我不知道上面的 eval
如何计入 "elegant" 但它确实让你从单一的和单独关心的 C 风格 assert
语句转向更易于管理的系统.
好的断言意味着并计划为系统测试和代码文档,但就其本质而言,它们缺乏正式的结构(因此最终可能仍然是分散的和临时的)。以这种方式完成后,它们会附带一个框架,并且可以作为一个套件使用许多工具进行管理和调整。