英文:
Define the same PHP function in many namespaces
问题
为了在单元测试中创建全局函数的模拟版本,我将其模拟版本定义在SUT类的命名空间中,如此处所解释的那样。
考虑一个全局函数 foo()
在 Bar
命名空间和 Baz
命名空间中都被使用的情况。如果我想在两个地方使用相同的 foo()
模拟版本,似乎我被迫要为 foo()
进行两次声明。
/* bar-namespace-mocks.php */
namespace Bar;
function foo(){
return 'foo mock';
}
/* baz-namespace-mocks.php */
namespace Baz;
function foo(){
return 'foo mock';
}
这段代码不符合DRY原则。更好的方式是只声明一次foo模拟版本。
/* foo-mock.php */
function foo(){
return 'foo mock';
}
然后按需要导入每个命名空间,如以下伪代码所示:
/* namespace-mocks.php */
namespace Bar{
import 'foo-mock.php';
}
namespace Baz{
import 'foo-mock.php';
}
使用include导入,例如
namespace Baz;
include 'foo-mock.php';
不会导致模拟在Baz
命名空间中被声明。是否有办法在多个命名空间中声明一个函数而不必多次版本化它?
英文:
In order to create a mock version of a global function for unit testing, I define its mock version in the namespace of the SUT class, as is explained here
Consider a case of global function foo()
which is used in namespace Bar
and in namespace Baz
. If I want to use the same mock version of foo()
in both places, it seems I am forced to make two declarations of the mock for foo()
/* bar-namespace-mocks.php */
namespace Bar;
function foo(){
return 'foo mock';
}
/* baz-namespace-mocks.php */
namespace Baz;
function foo(){
return 'foo mock';
}
This code does not conform to the DRY principal.
It would be preferred to have one declaration of the foo mock
/* foo-mock.php */
function foo(){
return 'foo mock';
}
and then import into each namespace as needed like the following pseudo code:
/* namespace-mocks.php */
namespace Bar{
import 'foo-mock.php';
}
namespace Baz{
import 'foo-mock.php';
}
Importing using include, e.g.
namespace Baz;
include 'foo-mock.php'
does not cause the mock to be declared in the Baz
namespace. Is there any way to declare a function in more than one namespace without having more than one version of it?
答案1
得分: 1
如果您需要对一个本地函数进行抽象化,那么请为其创建一个契约(contract),以便您可以使用依赖注入来提供其提供的服务:
interface FooInterface
{
public function get(): string;
}
然后提供一个默认的实现,使用本地函数:
class NativeFoo implements FooInterface
{
public function get(): string
{
return \foo();
}
}
然后您的主要类可能看起来像这样:
class A
{
protected FooInterface $foo;
public function __construct(FooInterface $foo = null)
{
$this->foo = $foo ?? new NativeFoo();
}
public function whatever()
{
$foo = $this->foo->get();
}
}
因此,在构造函数中有一个可选参数,如果您不提供值,将会得到本地的foo函数作为默认值。您只需执行以下操作:
$a = new A();
$a->whatever();
但如果您想要覆盖它,可以这样做:
$foo = new SomeOtherFoo();
$a = new A($foo);
$a->whatever();
然后,在您的测试中,您可以构建一个显式的模拟:
class MockFoo implements FooInterface
{
public function get(): string
{
return 'some test value';
}
}
并传递该实例:
$foo = new MockFoo();
$a = new A($foo);
$a->whatever();
或者使用PHPUnit模拟构建器:
$foo = $this->getMockBuilder(FooInterface::class)->... // 具体操作
如果您想要一个更简单的版本,您可以只使用一个可调用对象:
class A
{
protected $foo;
public function __construct(callable $foo = null)
{
$this->foo = $foo ?? 'foo';
}
public function whatever()
{
$foo = call_user_func($this->foo);
}
}
// 默认用法
$a = new A();
$a->whatever();
// 测试用法
$a = new A(fn() => 'some other value');
$a->whatever();
英文:
If you need to abstract away a native function, then make a contract for it, so that you can use dependency injection for the service it provides:
interface FooInterface
{
public function get(): string;
}
Then provide a default implementation that uses the native function:
class NativeFoo implements FooInterface
{
public function get(): string
{
return \foo();
}
}
Then your main class might look something like:
class A
{
protected FooInterface $foo;
public function __construct(FooInterface $foo = null)
{
$this->foo = $foo ?? new NativeFoo();
}
public function whatever()
{
$foo = $this->foo->get();
}
}
So, you've got an optional argument in the constructor, and if you don't provide a value, you'll get the native foo function as a default. You'd just do:
$a = new A();
$a->whatever();
But if you want to override that, you can do:
$foo = new SomeOtherFoo();
$a = new A($foo);
$a->whatever();
Then, in your test, you can build an explicit mock:
class MockFoo implements FooInterface
{
public function get(): string
{
return 'some test value';
}
}
And pass in instance of that:
$foo = new MockFoo();
$a = new A($foo);
$a->whatever();
Or use the PHPUnit mock builder:
$foo = $this->getMockBuilder(FooInterface::class)->... // whatever
If you want a simpler version, you could just use a callable:
class A
{
protected $foo;
public function __construct(callable $foo = null)
{
$this->foo = $foo ?? 'foo';
}
public function whatever()
{
$foo = call_user_func($this->foo);
}
}
// default usage
$a = new A();
$a->whatever();
// test usage
$a = new A(fn() => 'some other value');
$a->whatever();
答案2
得分: 0
I'll provide the translation of the code portion you provided:
在我全心全意同意Alex Howansky的答案的同时,如果你真的执意要使用纯函数,那么你总是可以:
```lang-php
namespace Mocks {
function foo() {
return 'foo';
}
}
namespace Bar {
function foo() {
return \Mocks\foo();
}
}
namespace Baz {
function foo() {
return \Mocks\foo();
}
}
namespace asdf {
var_dump(\Bar\foo(), \Baz\foo());
}
输出:
string(3) "foo"
string(3) "foo"
尽管在这一点上,我们实际上只是在重新发明类,但更糟糕。 在某个时候,你会想知道如何给变量命名空间,这是不可能的,所以你将在那一点上将其整合到类中。
类、接口、特性/继承将是最合适的解决这个问题的方式。
<details>
<summary>英文:</summary>
While I whole-heartedly agree with Alex Howansky's answer, if you're truly hell-bent on using plain functions, then you can always:
```lang-php
namespace Mocks {
function foo() {
return 'foo';
}
}
namespace Bar {
function foo() {
return \Mocks\foo();
}
}
namespace Baz {
function foo() {
return \Mocks\foo();
}
}
namespace asdf {
var_dump(\Bar\foo(), \Baz\foo());
}
Output:
string(3) "foo"
string(3) "foo"
Though at this point we're really just on our way to reinventing classes, but worse. At some point you're going to wonder how to namespace a variable, and that is simply not possible, so you'll be rolling this into classes at that point.
Classes, interfaces, and traits/inheritance would be most properly leveraged to solve this problem.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论