atoum/visibility-extension

atoum 扩展以放宽方法可见性

2.0.0 2021-02-09 18:11 UTC

This package is auto-updated.

Last update: 2024-09-12 21:15:17 UTC


README

atoum visibility-extension 允许你在单元测试中覆盖方法可见性。例如,你可以用它来测试受保护的方法。

示例

在示例中,我们测试了受保护的 bar 方法

<?php

namespace
{
  class foo
  {
    protected function bar()
    {
      return 'foo';
    }
  }
}

namespace tests\units
{
  use atoum\atoum;

  class foo extends atoum\test
  {
    public function testBar()
    {
      $this
        ->if($sut = new \foo())
        ->then
          ->string($this->invoke($sut)->bar())->isEqualTo('foo')
      ;
    }
  }
}

安装它

使用 composer 安装扩展

composer require --dev atoum/visibility-extension

扩展将自动加载。如果你希望卸载它,你可以在配置文件中添加以下内容

<?php

// .atoum.php

use atoum\atoum\visibility;

$runner->removeExtension(visibility\extension::class);

使用它

你可以使用两种方法来实现可见性覆盖

  • 覆盖具体类的成员方法可见性:这将允许你断言受保护方法的返回值
  • 覆盖模拟类的成员方法可见性:这将允许你覆盖受保护方法的代码并断言它们的调用

覆盖具体类的成员方法可见性

在单元测试的体中,通过使用 invoke 方法即时覆盖方法可见性

<?php

namespace
{
	class foo
	{
		protected function bar()
		{
			return $this;
		}
		
		protected function baz($arg)
		{
			return $arg;
		}
	}
}

namespace tests\units
{
	use atoum\atoum;

	class foo extends atoum\test
	{
		public function testBar()
		{
			$this
				->if($sut = new \foo())
				->then
					->object($this->invoke($sut)->bar())->isIdenticalTo($sut)
			;
		}
		
		public function testBaz()
		{
			$this
				->if($sut = new \foo())
				->and($arg = uniqid())
				->then
					->variable($this->invoke($sut)->baz($arg))->isEqualTo($arg)
			;
		}
	}
}

正如你所看到的,我们只使用了 invoke 方法。它有一个特殊的语法,我们将详细说明:$this->invoke(<对象实例>)-><方法名>(<参数>)

  • <对象实例> 是一个对象实例的引用。在上面的例子中是 $foo,一个指向 \foo 实例的引用;
  • <方法名> 是我们要使其可见并调用的方法的名称。在上面的例子中是 barbaz
  • <参数> 是你想要传递给方法的参数列表。在上面的例子中是 $arg,一个使用 uniqid() 生成的字符串。

覆盖模拟类的成员方法可见性

覆盖模拟类的成员方法需要做更多的工作,涉及模拟生成器。在详细说明如何实现之前,请注意,你必须了解一些限制。我们将在简短示例之后详细说明。

<?php

namespace
{
	class foo
	{
		public function baz()
		{
			return $this->bar();
		}

		protected function bar()
		{
			$args = func_get_args();

			return sizeof($args) ? $args : $this;
		}
	}
}

namespace tests\units
{
	use atoum\atoum;

	class foo extends atoum\test
	{
		public function testBar()
		{
			$this
				->given(
					$this->mockGenerator
						->makeVisible('bar')
						->generate('foo')
				)
				->if($mockedSut = new \mock\foo)
				->and($this->calling($mockedSut)->bar = 'foo')
				->then
					->string($mockedSut->baz())->isEqualTo('foo')
					->string($mockedSut->baz())->isEqualTo('foo')
					->mock($mockedSut)
						->call('bar')->twice()
			;
		}
	}
}

模拟生成器现在提供了一个 makeVisible 方法,你可以调用它来覆盖方法可见性。你必须在这个方法调用之前生成模拟类,这发生在第一次实例化模拟或调用模拟控制器中的 generate 方法时。

这样做将创建一个子类(模拟),并将受保护的方法定义为公共的。然后你可以直接调用它们,甚至不需要使用我们在上一节中看到的 invoke 方法。

你还可以使用标准的 atoum 断言来断言这些方法的调用。

现在让我们谈谈限制

  • 第一个限制是,可见性覆盖必须在模拟类的第一次生成之前声明,
  • 一旦覆盖了可见性,就不能撤销
  • 在模拟中覆盖方法的可见性需要谨慎操作:这是一个涉及反射的永久性操作。

当你想要暂时覆盖模拟类方法的可见性时,你可以使用 generate 方法的参数更改模拟类的名称。使用上一个例子,它看起来像这样

<?php

namespace tests\units
{
	use atoum\atoum;

	class foo extends atoum\test
	{
		public function testBar()
		{
			$this
				->given(
					$this->mockGenerator
						->makeVisible('bar')
						->generate('foo', 'mock', 'mockedFoo')
				)
				->if($mockedSut = new \mock\mockedFoo)
				->and($this->calling($mockedSut)->bar = 'foo')
				->then
					->string($mockedSut->baz())->isEqualTo('foo')
					->string($mockedSut->baz())->isEqualTo('foo')
					->mock($mockedSut)
						->call('bar')->twice()
			;
		}
	}
}

这样做,我们可以从\foo类生成一个具有更宽松可见性的\mock\mockedFoo类,用于bar方法。这将允许我们绕过一些限制

  • 这将生成一个新的模拟类,因此即使\foo类已经被模拟,可见性覆盖也将始终适用
  • 我们可以通过将这个模拟类视为一次性模拟并在测试后立即忘记它来“撤销”这个操作。这仍然需要我们不要为未来的模拟重用相同的名称。

链接

许可

visibility-extension项目是在BSD-3条款许可下发布的。有关详细信息,请参阅捆绑的LICENSE文件。

atoum