请帮我对该功能进行单元测试
func_for_test
。
我不明白如何模拟 get_dbh
函数(我使用 Test::Spec)。
ModuleForTests.pm
-----------------
use ModuleMysql;
my $dbh = get_dbh();
sub **func_for_test** {
my $sql = "DELETE ...";
$dbh->prepare($sql);
...
}
ModuleMysql.pm
-----------------
sub get_dbh {
state $dbh = DBI->connect ...
return $dbh;
}
我尝试了 DBD::Mock,但没有帮助。
$drh = DBI->install_driver( 'Mock' );
$drh->{mock_connect_fail} = 0;
$dbh = DBI->connect( 'DBI:Mock:', '', '' );
我也尝试过,但没有帮助 - 错误 Test::Spec;
my $dbh;
BEGIN {
$dbh = mock();
ModuleForTests->stubs(get_dbh => $dbh);
}
使用 Test::Spec 迫使您以不同的方式编写代码(许多人认为这是正确的方式)。
如果你想使用 Test::Spec,最好对你的类使用面向对象的设计。 ModuleForTest 中填充的 Lexical
$dbh
模拟起来很复杂,最好将数据库句柄存储在对象属性中。但它是 Perl,一切皆有可能。
#!/usr/bin/perl
use warnings;
use strict;
use Test::Spec;
use Sub::Override;
use DBI;
use ModuleMysql; # Needed here so we can override its get_dbh.
my $dbh;
BEGIN {
$dbh = 'DBI'->connect('DBI:Mock:', "", "") or die $DBI::errstr;
}
my $override;
BEGIN {
$override = 'Sub::Override'->new('ModuleMysql::get_dbh' => sub {
return $dbh
});
}
use ModuleForTest;
describe ModuleForTest => sub {
it 'prepares delete' => sub {
my $sth = ModuleForTest::func_for_test();
my $history = $dbh->{mock_all_history};
is($history->[0]->statement, 'DELETE ...');
};
};
runtests();
需要
BEGIN
块,因为我们需要在ModuleMysql加载之后、MoudleForTest调用它之前的精确时刻重新定义get_dbh。
我不会解决问题的
Test::Spec
部分,所以这可能对您的特定情况没有用。不过,其他来的人可能会发现它很有用。
问题在于类中有一个方法执行了一些妨碍测试的操作。显而易见的解决办法是让这不再成为问题,但我们假设我们一直坚持这个问题。这可能是您正在使用不想更改的第三方代码的情况,因为安装更新后您的更改可能会消失。
其中之一就像一个神奇的模块正在为你做的事情。我倾向于不使用模块,直到其价值超过依赖管理的缺点(而
Test::
模块往往是最糟糕的)。
package ModuleForTest::WithoutGetDBH;
use parent ModuleForTest;
sub get_dbh { ... whatever you need ... }
乍一看,这似乎没有其他神奇技术那么有吸引力,但你已经用模拟改变了类的含义。某些地方必须改变,所以为什么不使用最简单和最有意的方式来拥有一个不同的、更具体的类版本呢?
有理由不这样做。有些模块不是为子类化而设计的。也就是说,作者做出了防止子类化的特定决定。
我经常简单地替换掉麻烦的方法。 Perl 毕竟是一种动态语言,所以让我们使用一些动态特性。在等待应用和发布补丁时,我倾向于使用第三方代码来执行此操作。我知道我稍后会删除它,所以我不想像子类模块那样更改包名称。
先加载模块,然后替换定义。最后一个定义获胜:
BEGIN {
use ModuleForTest;
no strict 'refs';
*{"ModuleForTest::get_dbh"} = sub { ... whatever you want ... }
}
有时我在本地执行此操作,但通常仅使用我完全控制的代码,并且出于某种原因,我想尝试一堆不同的情况(例如失败的情况、成功的情况、抛出异常的情况等等)。由于这些测试通常是按顺序运行的,因此这是一种专门针对这种情况的特殊技术:
use ModuleForTest
subtest 'something' => sub {
local *{"ModuleForTest::get_dbh"} = sub { ... whatever you want ... };
}