如何使用 Test::Spec 模拟方法?

问题描述 投票:0回答:2

请帮我对该功能进行单元测试

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);
}
perl testing mocking
2个回答
1
投票

使用 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。


0
投票

我不会解决问题的

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 ... };
    }

© www.soinside.com 2019 - 2024. All rights reserved.