【问题标题】:Unit test persistence layer - Symfony单元测试持久层 - Symfony
【发布时间】:2016-05-04 00:20:07
【问题描述】:

我想在 Symfony2 中测试持久性。我想知道它是更好的模拟实体并提供给实体经理,还是更好的模拟实体经理并将实体传递给经理?

我的第一个选项,但实体管理器抛出异常而不是对象不是实体学说。如何在 PHPUNIT 中测试持久性 symfony?

【问题讨论】:

    标签: php symfony phpunit


    【解决方案1】:

    您应该为持久层编写集成测试,而不是编写单元测试

    单元测试中有一条规则“不要嘲笑你不拥有的东西”。

    您不拥有 Doctrine 类或接口,并且您永远无法确定您对模拟的接口所做的假设是正确的。即使在您编写测试时它们是正确的,您也无法确定 Doctrine 的行为不会随着时间而改变。

    每当您使用第 3 方代码时,您都应该为任何使用它的东西编写集成测试。集成测试实际上会调用数据库并确保学说按照您认为的方式工作。

    这就是为什么最好与第 3 方的东西分离。

    就教义而言,这很容易。您可以做的其中一件事是为每个存储库引入一个接口。

    interface ArticleRepository
    {
        /**
         * @param int $id
         *
         * @return Article
         *
         * @throws ArticleNotFoundException
         */
        public function findById($id);
    }
    

    您可以轻松地模拟或存根这样的接口:

    class MyServiceTest extends \PHPUnit_Framework_Test_Case
    {
        public function test_it_does_something()
        {
            $article = new Article();
            $repository = $this->prophesize(ArticleRepository::class);
            $repository->findById(13)->willReturn($article);
    
            $myService = new MyService($repository->reveal());
            $myService->doSomething();
    
            // ...
        }
    }
    

    接口将用学说实现:

    use Doctrine\ORM\EntityRepository;
    
    class DoctrineArticleRepository extends EntityRepository implement ArticleRepository
    {
        public function findById($id)
        {
            // call doctrine here
        }
    }
    

    实现是您要为其编写集成测试的目的。在存储库的集成测试中,您实际上会调用数据库:

    use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
    
    class ArticleTest extends KernelTestCase
    {
        private $em;
        private $repository;
    
        protected function setUp()
        {
            self::bootKernel();
    
            $this->em = static::$kernel->getContainer()
                ->get('doctrine')
                ->getManager();
            $this->repository = $this->em->getRepository(Article::class);
        }
    
        public function test_it_finds_an_article()
        {
             $this->createArticle(13);
    
             $article = $this->repository->findById(13);
    
             $this->assertInstanceOf(Article::class, $article);
             $this->assertSame(13, $article->getId());
        }
    
        /**
         * @expectedException ArticleNotFoundException
         */
        public function test_it_throws_an_exception_if_article_is_not_found()
        {
            $this->repository->findById(42);
        }
    
        protected function tearDown()
        {
            parent::tearDown();
    
            $this->em->close();
        }
    
        private function createArticle($id)
        {
            $article = new Article($id);
            $this->em->persist($article);
            $this->em->flush();
            // clear is important, otherwise you might get objects stored by doctrine in memory
            $this->em->clear();
        }
    }
    

    在其他任何地方,您都将使用将被存根(或模拟)的接口。在其他任何地方,您都不会访问数据库。这使您的测试速度更快。

    您的存根中的实体可以简单地创建或存根。大多数时候我创建实体对象而不是模拟它们。

    【讨论】:

    • 我什至会更进一步,让我的 ArticleRepository 包装 Doctrine 层,而不是扩展 EntityRepository。否则这个类的实例化仍然是你无法控制的,需要它们的类仍然依赖于 EntityManager 来访问它
    • 包装确实是一种选择。在许多情况下更好,因为这样您可以将其他协作者以及实体管理器注入到您的存储库中!
    • 您会使用内存中的 SQLite 驱动程序还是生产环境中使用的相同驱动程序?
    • @M4ver 引入存储库接口后,您就可以编写单元测试了!只是不要忘记为接口的实现编写一个集成测试。对于其他所有内容,您编写一个单元测试。
    • @noisebleed 我使用相同的驱动程序。使用不同的驱动程序与模拟相同:您无法保证 Doctrine 的行为完全相同(另外,如果您直接使用 SQL,这将迫使您以跨平台方式编写任何 SQL 查询)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-01
    • 2015-02-09
    • 1970-01-01
    • 2019-06-04
    • 2014-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多