在 Raku 中进行单元测试时如何模拟 class 方法
How to mock a class method when unittesting in Raku
假设我有一个这样的class:
class MyClass {
method data-is-valid {
return self!get-data ~~ m{^From};
}
method !get-data {
return 'From Internet';
}
}
其中 !get-data
方法从 Internet 获取一些数据。
是否可以模拟该方法,使其 returns 我自己的硬编码数据,这样我就可以在不连接到 Internet 的情况下测试模块?
理想情况下,解决方案不应以任何方式修改 class 的定义。
注意:similar question 存在关于模块的单元测试子例程。
你好@julio 我建议你看一下例程的 wrap 函数,这应该可以满足你的需要... https://docs.raku.org/language/functions#Routines ... 这包括 use soft;防止内联的杂注
您可能想看一看 Test::Mock。从它的概要:
use Test;
use Test::Mock;
plan 2;
class Foo {
method lol() { 'rofl' }
method wtf() { 'oh ffs' }
}
my $x = mocked(Foo);
$x.lol();
$x.lol();
check-mock($x,
*.called('lol', times => 2),
*.never-called('wtf'),
);
我会首先重构以将获取逻辑拉出到另一个对象,并使 MyClass
依赖于它:
class Downloader {
method get-data {
return 'From Internet';
}
}
class MyClass {
has Downloader $.downloader .= new;
method data-is-valid {
return $!downloader.get-data ~~ m{^From};
}
}
这是依赖倒置的一个例子,这是一种使代码可测试的有用技术(并且往往也更容易以其他方式发展)。
通过此更改,现在可以使用 Test::Mock 模块模拟 Downloader
:
use Test;
use Test::Mock;
subtest 'Is valid when contains From' => {
my $downloader = mocked Downloader, returning => {
get-data => 'From: blah'
};
my $test = MyClass.new(:$downloader);
ok $test.data-is-valid;
check-mock $downloader,
*.called('get-data', :1times);
}
subtest 'Is not valid when response does not contain From' => {
my $downloader = mocked Downloader, returning => {
get-data => 'To: blah'
};
my $test = MyClass.new(:$downloader);
nok $test.data-is-valid;
check-mock $downloader,
*.called('get-data', :1times);
}
最好的办法可能是重构代码(参见 Jonathan 的回答)
但是,如果由于某种原因你不能,还有其他选择:
如果方法是 public 您可以简单地创建一个子类并覆盖该方法。
例如:
use Test;
class MyClass {
method data-is-valid {
return self.get-data ~~ m{^From};
}
method get-data {
return 'From Internet';
}
}
class MyClassTester is MyClass {
method get-data {
return 'Foobar';
}
}
my MyClassTester $class = MyClassTester.new;
nok $class.data-is-valid, 'Mocked class has invalid data';
done-testing;
如果该方法是私有的,您可以使用 wrap
,如 p6steve 的回答所述。但是,您需要自省才能修改私有方法。
可以这样做:
use Test;
class MyClass {
method data-is-valid {
return self!get-data ~~ m{^From};
}
method !get-data {
return 'From Internet';
}
}
my $class = MyClass.new;
my Method:D $get-data = $class.^find_private_method: 'get-data';
$get-data.wrap: { 'Foobar' };
nok $class.data-is-valid, 'Mocked class has invalid data';
done-testing;
假设我有一个这样的class:
class MyClass {
method data-is-valid {
return self!get-data ~~ m{^From};
}
method !get-data {
return 'From Internet';
}
}
其中 !get-data
方法从 Internet 获取一些数据。
是否可以模拟该方法,使其 returns 我自己的硬编码数据,这样我就可以在不连接到 Internet 的情况下测试模块?
理想情况下,解决方案不应以任何方式修改 class 的定义。
注意:similar question 存在关于模块的单元测试子例程。
你好@julio 我建议你看一下例程的 wrap 函数,这应该可以满足你的需要... https://docs.raku.org/language/functions#Routines ... 这包括 use soft;防止内联的杂注
您可能想看一看 Test::Mock。从它的概要:
use Test;
use Test::Mock;
plan 2;
class Foo {
method lol() { 'rofl' }
method wtf() { 'oh ffs' }
}
my $x = mocked(Foo);
$x.lol();
$x.lol();
check-mock($x,
*.called('lol', times => 2),
*.never-called('wtf'),
);
我会首先重构以将获取逻辑拉出到另一个对象,并使 MyClass
依赖于它:
class Downloader {
method get-data {
return 'From Internet';
}
}
class MyClass {
has Downloader $.downloader .= new;
method data-is-valid {
return $!downloader.get-data ~~ m{^From};
}
}
这是依赖倒置的一个例子,这是一种使代码可测试的有用技术(并且往往也更容易以其他方式发展)。
通过此更改,现在可以使用 Test::Mock 模块模拟 Downloader
:
use Test;
use Test::Mock;
subtest 'Is valid when contains From' => {
my $downloader = mocked Downloader, returning => {
get-data => 'From: blah'
};
my $test = MyClass.new(:$downloader);
ok $test.data-is-valid;
check-mock $downloader,
*.called('get-data', :1times);
}
subtest 'Is not valid when response does not contain From' => {
my $downloader = mocked Downloader, returning => {
get-data => 'To: blah'
};
my $test = MyClass.new(:$downloader);
nok $test.data-is-valid;
check-mock $downloader,
*.called('get-data', :1times);
}
最好的办法可能是重构代码(参见 Jonathan 的回答)
但是,如果由于某种原因你不能,还有其他选择:
如果方法是 public 您可以简单地创建一个子类并覆盖该方法。
例如:
use Test;
class MyClass {
method data-is-valid {
return self.get-data ~~ m{^From};
}
method get-data {
return 'From Internet';
}
}
class MyClassTester is MyClass {
method get-data {
return 'Foobar';
}
}
my MyClassTester $class = MyClassTester.new;
nok $class.data-is-valid, 'Mocked class has invalid data';
done-testing;
如果该方法是私有的,您可以使用 wrap
,如 p6steve 的回答所述。但是,您需要自省才能修改私有方法。
可以这样做:
use Test;
class MyClass {
method data-is-valid {
return self!get-data ~~ m{^From};
}
method !get-data {
return 'From Internet';
}
}
my $class = MyClass.new;
my Method:D $get-data = $class.^find_private_method: 'get-data';
$get-data.wrap: { 'Foobar' };
nok $class.data-is-valid, 'Mocked class has invalid data';
done-testing;