让我们考虑一下这个场景
public class FooManager()
{
//somewhere in my manager class code
public function merge(Foo $a, Foo $b, $id)
{
if ($a->getId() == $id) { //!! PAY ATTENTION !!
}
//and so on
}
}
我在很多地方都读到模拟实体不是一个好的实践,但是我想知道这段特定的代码。
如果我不能给一个真实的实体对象分配一个Id(例如,它是根据原则自动生成的),我如何在不模拟Foo
实体的情况下测试这个功能?
我正在思考这个问题,然后突然闪了一下。在我看来:我在这里测试的是FooManager
,而不是实体。因此,对我来说,使用mock不是唯一的解决方案,但可能是最好的解决方案。有人能帮我理解一下我的思维过程是不是很好?
更新2
我之前没有提到过,但是,当然,我需要测试$a
和$b
的变化($a
将从$b
接收一些属性值,并将相应地更新其属性)。这是测试的目标,因为FooManager
的目标是将$b
属性合并到$a
属性中(当然要应用一些逻辑)
如果您需要绕过doctrine的自动生成实体id策略,您可以通过元数据操作,并设置一个已知id。例如,如果你用DoctrineFixtureBundle加载你的测试fixture,你可以在你的fixture中有:
class FooFixtures extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
$metadata = $manager->getClassMetaData("Acme\DemoBunde\Entity\Foo");
$metadata->setIdGeneratorType(DoctrineORMMappingClassMetadata::GENERATOR_TYPE_NONE);
...
$foo = new Foo();
$foo ->setId(3);
$foo ->setTitle('Foo example');
然后在测试类
中使用它public function setUp()
{
parent::setUp();
$this->fooFixtures = new FooFixtures();
$this->loadFixtures(array($this->fooFixtures));
}
现在数据库中有一个id为3的Foo实例
希望这对你有帮助,我理解你的需要
免责声明:使用数据库id!!!!硬编码测试不是一个好做法
我的手。
我的第一个想法,正如我前面所说的,是创建一个mock,因为phpspec +原则似乎迫使我这样做。
过了一会儿,然而,我遇到了一个混乱的- >getFoo()->hasToBeCalled()
, ->setFoo()->hasToBeCalled()
序列,其中FooManager
"触摸"的所有属性都需要一个期望。这迫使我重新考虑这个解决方案,不,对我来说不是最好的。
我已经检查了我的类,事实是SUT不是我想在这里直接测试的,但我需要检查的是这个动作对实体的影响(无论如何是一种行为,对吗?),我决定按如下步骤进行:
-
创建
TestFoo
实体(在specfolderToMySpecFooSpec.php
内部)class TestFoo extends Foo { public function setId($id) { $this->id = $id } }
这样我就可以直接设置
$id
(不是我需要测试的行为作为学说,在正常情况下会为我做),并保持这个类在我的/src
代码库之外 -
我已经修改了我的
merge()
函数,只返回我需要检查测试目的的实体(而且,不,我不只是为了测试的缘故,因为有一些关于合并实体的信息可能是有用的) -
最后,使用直接从SUT返回的这个实体,我可以像通常使用SUT本身一样使用预言匹配器。
这个解决方案有很多优点,因为我不需要编写每个将被模拟实体调用的方法,并且让我可以自由地使用返回的存根只测试我真正需要测试的内容。
任何想法吗?
如果您需要为写测试创建mock,那么创建它是正常的。
在这种具体情况下,您需要存根。我推荐阅读这篇可爱的文章来解释mock和mock之间的区别。存根。
Robert C. Martin说:模拟跨架构上重要的边界,但不能在这些边界内模拟。你可以从这一点入手进行研究。
和我的意见:因为在这种情况下,你需要创建一个模拟写正确的测试-创建它。我看不出这有什么不好。