Unit testing 如何模拟完整模块?

Unit testing 如何模拟完整模块?,unit-testing,perl,mocking,Unit Testing,Perl,Mocking,我想测试一个模块B。此模块依赖于我不想测试的模块A。下面是我的软件包B的一个示例: use strict; use warnings FATAL => 'all'; use A; package B; sub to_test { return A::a(); } 在我的单元测试中,我想调用B::to_test,但我想手动指定A::A()必须返回的值。为此,我编写了一个模块mock.pm,其中包含aTo mock的所有子模块: use strict; use warn

我想测试一个模块
B
。此模块依赖于我不想测试的模块
A
。下面是我的软件包
B
的一个示例:

use strict;
use warnings FATAL => 'all';

use A;

package B;
    
sub to_test {
    return A::a();
}
在我的单元测试中,我想调用
B::to_test
,但我想手动指定
A::A()
必须返回的值。为此,我编写了一个模块
mock.pm
,其中包含
a
To mock的所有子模块:

use strict;
use warnings 'FATAL' => 'all';

package Mock::A;

sub a {
    return "From mock";
}
    
1;
我知道我可以使用轻松地模拟
A::A
,问题是包
A
可能包含很多子组件,并且
Test::MockModule
允许我逐个模拟它们


如何使用
moka
模拟完整的
A
包?

可以通过
moka
中的sub重新定义包
A
中定义的sub。为此,有三个条件:

  • MockA
    必须定义与
    A
    相同的包
  • MockA
    必须包含在
    A
    的所有包含内容之后
  • 警告<代码>重新定义不能是致命的
文件
MockA.pm
可以是以下文件:

use strict;
# No fatal error if a sub is redefined
use warnings 'FATAL' => 'all', 'NONFATAL' => 'redefine';

# Override package A
package A;

sub a {
    return "From mock";
}
    
1;
在单元测试中:

use B; # B includes A
use MockA; # MockA overrides A

B::to_test() # B calls MockA::a

可以通过
moka
中的sub重新定义包
A
中定义的sub。为此,有三个条件:

  • MockA
    必须定义与
    A
    相同的包
  • MockA
    必须包含在
    A
    的所有包含内容之后
  • 警告<代码>重新定义不能是致命的
文件
MockA.pm
可以是以下文件:

use strict;
# No fatal error if a sub is redefined
use warnings 'FATAL' => 'all', 'NONFATAL' => 'redefine';

# Override package A
package A;

sub a {
    return "From mock";
}
    
1;
在单元测试中:

use B; # B includes A
use MockA; # MockA overrides A

B::to_test() # B calls MockA::a

现有的答案显示了如何替换模块的部分,这是非常明智的

但是,如果您想完全更换模块(甚至不需要安装原装模块),可以使用以下方法:

use strict;
# No fatal error if a sub is redefined
use warnings 'FATAL' => 'all', 'NONFATAL' => 'redefine';

# Override package A
package A;

sub a {
    return "From mock";
}
    
1;
#使用我们正在模拟的包的名称。
A包;
...
#告诉Perl我们正在模拟的模块已经加载。
$INC{({uuuu PACKAGE}~s{::}{/}rg)。'.pm'}=1;

使用MockA必须出现在
的任何实例之前使用

现有答案显示了如何更换模块的部件,这是非常明智的

但是,如果您想完全更换模块(甚至不需要安装原装模块),可以使用以下方法:

use strict;
# No fatal error if a sub is redefined
use warnings 'FATAL' => 'all', 'NONFATAL' => 'redefine';

# Override package A
package A;

sub a {
    return "From mock";
}
    
1;
#使用我们正在模拟的包的名称。
A包;
...
#告诉Perl我们正在模拟的模块已经加载。
$INC{({uuuu PACKAGE}~s{::}{/}rg)。'.pm'}=1;

使用MockA必须出现在
的任何实例之前使用

您可能也想查看Sub::Override。@simbabque我已经查看了这个模块,但没有找到任何方法来模拟整个包。有可能做到吗?没有,那只是做了和你在那里做的一样的事情,但是用更少的
没有严格的
s。MockModule基本上是一样的。你不想嘲笑整件事吧?你想在B中运行你的代码,并且你想为A中的调用为特定的B中的所有东西注入依赖项。你必须先加载真实的东西,否则它会覆盖你所做的更改。另一方面,一个正在做的事情是否涉及到外部依赖?或者它是否提供了对代码中分支的访问,而您只能通过更改其返回值来测试这些分支?您可能还需要查看Sub::Override。@simbabque我已经查看了这个模块,但没有找到任何方法来模拟整个包。有可能做到吗?没有,那只是做了和你在那里做的一样的事情,但是用更少的
没有严格的
s。MockModule基本上是一样的。你不想嘲笑整件事吧?你想在B中运行你的代码,并且你想为A中的调用为特定的B中的所有东西注入依赖项。你必须先加载真实的东西,否则它会覆盖你所做的更改。另一方面,一个正在做的事情是否涉及到外部依赖?或者它是否提供对代码中只能通过更改其返回值来测试的分支的访问?请尝试test::MockObject。是的,它与Test::MockModule有一个类似的名称,但它是一个不同的概念。两者都很有用,可以相互配合使用。但在您的示例中,真正的问题是缺少依赖注入。模块B不应该盲目地调用
A::A()
。相反,您应该将一个对象传递给模块B,模块B应该执行
$object->a()
。这样,您就可以提供一个轻量级的模拟对象,而不是真正的对象。是的,它与Test::MockModule有一个类似的名称,但它是一个不同的概念。两者都很有用,可以相互配合使用。但在您的示例中,真正的问题是缺少依赖注入。模块B不应该盲目地调用
A::A()
。相反,您应该将一个对象传递给模块B,模块B应该执行
$object->a()
。这样,您就可以提供一个轻量级的模拟对象,而不是真实的对象。