如何用Test::Spec模拟get_dbh函数编写func_for_test单元测试?
解决Test::Spec中模拟get_dbh的问题
问题根源
你的ModuleForTests.pm在编译阶段就执行了my $dbh = get_dbh();,后续对get_dbh的mock根本影响不到已经初始化的$dbh变量,这就是之前尝试的方法全部失效的原因。
正确Mock方案
方案一:提前mock ModuleMysql模块
在加载ModuleForTests之前,先通过Test::MockModule替换ModuleMysql的get_dbh函数,让它返回我们的mock数据库句柄。
use Test::Spec; use Test::MockModule; use DBD::Mock; # 先mock ModuleMysql,再加载目标模块 my $mock_mysql = Test::MockModule->new('ModuleMysql'); my $mock_dbh = DBI->connect('DBI:Mock:', '', ''); $mock_mysql->mock('get_dbh', sub { return $mock_dbh }); # 此时加载ModuleForTests,它会使用mock后的get_dbh赋值$dbh use ModuleForTests; describe "func_for_test" => sub { it "应执行DELETE语句" => sub { func_for_test(); # 验证SQL执行记录 my @statements = $mock_dbh->{mock_all_history}; is(scalar @statements, 1, "执行了1条SQL语句"); like($statements[0]->statement, qr/DELETE/, "SQL为DELETE语句"); }; }; runtests unless caller;
方案二:直接修改ModuleForTests的$dbh变量
如果不想提前mock模块,可以直接通过符号表访问并修改ModuleForTests中已初始化的$dbh变量:
use Test::Spec; use ModuleForTests; use DBD::Mock; describe "func_for_test" => sub { before each => sub { # 创建mock数据库句柄 my $mock_dbh = DBI->connect('DBI:Mock:', '', ''); # 修改ModuleForTests包中的$dbh变量 no strict 'refs'; ${'ModuleForTests::dbh'} = $mock_dbh; }; it "应正确准备DELETE语句" => sub { func_for_test(); no strict 'refs'; my @history = ${'ModuleForTests::dbh'}->{mock_all_history}; ok(grep { $_->statement =~ /DELETE/ } @history, "已执行DELETE语句"); }; }; runtests unless caller;
之前方法失效的原因
- 直接用
DBD::Mock连接无效:get_dbh内部的DBI->connect调用和你单独创建的mock句柄无关,无法覆盖原有的数据库连接。 - 直接stubs
ModuleForTests->get_dbh无效:get_dbh是ModuleMysql的函数,且ModuleForTests在编译时已经将get_dbh的返回值存入$dbh,后续mock无法改变这个已初始化的变量。
内容的提问来源于stack exchange,提问作者Вета Хмелёва
相关产品推荐
相关产品推荐

