gpt4 book ai didi

Symfony2 单元测试服务

转载 作者:行者123 更新时间:2023-11-28 19:58:01 25 4
gpt4 key购买 nike

我对 symfony 还是很陌生,但真的很喜欢它。

我正处于成功创建和设置服务的阶段,该服务本身使用 2 个依赖项:

  • 一个返回 json 数据的数据 API(这是一个单独的库,它我已经作为一项服务实现并附带了自己的单元测试)。
  • Doctrine 实体经理。

该服务使用 api 提取所需的数据,然后遍历数据并检查数据是否已经存在,如果存在则更新现有实体并持久化它,否则它创建一个新实体分配数据和坚持下去。

我现在需要为此编写一个单元测试,我没有仅使用 symfony2 教程中的 PHPUnit,这些教程正在测试来自 Controller 的响应。

我如何着手为此服务编写单元测试?特别是模拟我通常从 api 中提取的数据。然后检查条目是否需要更新或创建?

代码示例会非常有用,因此我可以将其用作模板来为我创建的其他类似服务创建测试。

这是我要测试的服务:

<?php

namespace FantasyPro\DataBundle\DataManager;

use Doctrine\ORM\EntityManager;
use FantasyDataAPI\Client;
use FantasyPro\DataBundle\Entity\Stadium;

class StadiumParser {
/**
* @var EntityManager $em
*/
private $em;
/**
* @var Client $client
*/
private $client;

public function __construct( EntityManager $em, Client $client) {
$this->em = $em;
$this->client = $client;
}

/**
* @return array
*/
public Function parseData(){

//var_dump($this);
$stadiumData = $this->client->Stadiums();
//var_dump($stadiumData);
//get the Repo
$repo = $this->em->getRepository('DataBundle:Stadium');

$log = array();

foreach ($stadiumData as $stadium) {
// Get the current stadium in the list from the database
$criteria = array( 'stadiumID' => $stadium['StadiumID'] );
$currentStadium = $repo->FindOneBy( $criteria );

if ( ! $currentStadium) {
$currentStadium = new Stadium(); //no stadium with the StadiumID exists so create a new stadium

$logData = [
'action' => 'Added Stadium',
'itemID' => $stadium['StadiumID'],
'itemName' => $stadium['Name']
];
$log[] = $logData;
} else {
$logData = [
'action' => 'Updated Stadium',
'itemID' => $stadium['StadiumID'],
'itemName' => $stadium['Name']
];
$log[] = $logData;
}
$currentStadium->setStadiumID( $stadium['StadiumID'] );
$currentStadium->setName( $stadium['Name'] );
$currentStadium->setCity( $stadium['City'] );
$currentStadium->setState( $stadium['State'] );
$currentStadium->setCountry( $stadium['Country'] );
$currentStadium->setCapacity( $stadium['Capacity'] );
$currentStadium->setPlayingSurface( $stadium['PlayingSurface'] );
$this->em->persist( $currentStadium );
}
$this->em->flush();
return $log;
}
}

****** 更新 *******阅读 ilpaijin 的回答后。

我已经简化了服务,所以它不再返回日志,我最初有这个,所以我可以通过将日志发送到我的 Controller 中的 Twig 模板来检查添加了什么,我最终计划让它运行一个命令,因此我可以通过 cron 作业运行它,因此不需要日志位。

我现在正在我的构造中设置实体,因为我不知道如何将实体作为注入(inject)的依赖项传递。 现在使用 createNewStadium() 方法获取一个新实体。

更新后的服务:

namespace FantasyPro\DataBundle\DataManager;

use Doctrine\ORM\EntityManager;
use FantasyDataAPI\Client;
use FantasyPro\DataBundle\Entity\Stadium;

class StadiumParser {
/**
* @var EntityManager $em
*/
private $em;
/**
* @var Client $client
*/
private $client;
/**
* @var Stadium Stadium
*/
private $stadium;

public function __construct( EntityManager $em, Client $client) {
$this->em = $em;
$this->client = $client;
}

/**
* Gets a list of stadiums using $this->client->Stadiums.
* loops through returned stadiums and persists them
* when loop has finished flush them to the db
*/
public Function parseData(){
$data = $this->client->Stadiums();
//get the Repo
$repo = $this->em->getRepository('DataBundle:Stadium');

foreach ($data as $item) {
// Get the current stadium in the list
$criteria = array( 'stadiumID' => $item['StadiumID'] );
$currentStadium = $repo->FindOneBy( $criteria );

if ( ! $currentStadium) {
$currentStadium = $this->createNewStadium; //no stadium with the StadiumID use the new stadium entity
}
$currentStadium->setStadiumID( $item['StadiumID'] );
$currentStadium->setName( $item['Name'] );
$currentStadium->setCity( $item['City'] );
$currentStadium->setState( $item['State'] );
$currentStadium->setCountry( $item['Country'] );
$currentStadium->setCapacity( $item['Capacity'] );
$currentStadium->setPlayingSurface( $item['PlayingSurface'] );
$this->em->persist( $currentStadium );
}
$this->em->flush();
}

// Adding this new method gives you the ability to mock this dependency when testing
private function createNewStadium()
{
return new Stadium();
}
}

最佳答案

您基本上需要的是使用所谓的“Test doubles”对服务进行单元测试。

这意味着您应该模拟您的服务所具有的依赖关系,这样您就可以仅测试孤立的服务而无需真正依赖依赖项,而只能使用它们的模拟版本,使用硬编码的值或行为。

基于您的实际实现的真实示例是不可能的,因为您有紧密耦合的部门,如 $currentStadium = new Stadium();。您应该在构造函数中或通过 getter/setter 传递像这样的 dep,以便能够在单元测试时模拟它。

一旦完成,一个非常具有指示性的示例将是:

// class StadiumParser revisited and simplified
class StadiumParser
{
private $client;

public function __construct(Client $client)
{
$this->client = $client;
}

public function parseData()
{
$stadiumData = $this->client->Stadiums();

// do something with the repo

$log = array();

foreach ($stadiumData as $stadium) {
$logData = [
'action' => 'Added Stadium',
'itemID' => $stadium['StadiumID'],
'itemName' => $stadium['Name']
];
$log[] = $logData;
}

// do something else with Doctrine

return $log;
}
}

和测试

// StadiumParser Unit Test
class StadiumParserTest extends PHPUnit_Framework_TestCase
{
public function testItParseDataAndReturnTheLog()
{
$client = $this->getMock('FantasyDataAPI\Client');

// since you class is returning a log array, we mock it here
$expectedLog = array(
array(
'action' => 'Added Stadium',
'itemID' => $stadium['StadiumID'],
'itemName' => $stadium['Name']
)
);

// this is the mocked or test double part.
// We only need this method return something without really calling it
// So we mock it and we hardcode the expected return value
$stadiumData = array(
array(
"StadiumID" => 1,
"Name" => "aStadiumName"
)
);

$client->expects($this->once())
->method('Stadiums')
->will($this->returnValue($stadiumData));

$stadiumParser = new StadiumParser($client);

$this->assertEquals($expectedLog, $stadiumParser->parseData());
}
}

我自愿省略了 EntityManager 部分,因为我想你应该看看与 how to unit test code interacting with the database 相关的 Symfony 文档


-----编辑2-----

是的,他是对的,你不应该。想到的一种可能方法是在 protected /私有(private)方法中提取实体的创建。像这样的东西:

// class StadiumParser
public Function parseData()
{
...

foreach ($stadiumData as $stadium) {
...

if ( ! $currentStadium) {
$currentStadium = $this->createNewStadium();
...
}

// Adding this new method gives you the ability to mock this dependency when testing
private function createNewStadium()
{
return new Stadium();
}

-----编辑3-----

我想向您推荐另一种方法。如果 Stadium 实体在不同的服务或相同的不同部分中需要,这可能是一个更好的选择。我提议的是 Builder模式但一个Factory也可以在这里选择。浏览一下它们的差异。正如您所看到的,这从方法中提取了一些代码,更好地分配类之间的责任,让您和您的团队成员更清晰、更容易阅读。而且您已经知道如何在测试时模拟它。

class StadiumParser 
{
private $stadiumBuilder;
...

public function __construct( StadiumBuilder $builder, ...) {
$this->stadiumBuilder = $stadiumBuilder;
...
}

public Function parseData()
{
...

foreach ($stadiumData as $stadium) {
...
$currentStadium = $repo->FindOneBy( $criteria );

if ( ! $currentStadium) {
$currentStadium = $this->stadiumBuilder->build($currentStadium, $stadium);
}

$this->em->persist($currentStadium);
...

在某处你有这个新的 Builder 返回一个 Stadium 实例。这样,您的 StadiumParser 服务就不再与实体耦合,而 StadiumBuilder 就是它。逻辑是这样的:

// StadiumBuilder class

namespace ???

use FantasyPro\DataBundle\Entity\Stadium;

class StadiumBuilder
{
// depending on the needs but this should also has a different name
// like buildBasic or buildFull or buildBlaBlaBla or buildTest
public function build($currentStadium = null, $stadium)
{
if (!$currentStadium) {
$currentStadium = new Stadium();
}

$currentStadium->setStadiumID( $stadium['StadiumID'] );
$currentStadium->setName( $stadium['Name'] );
$currentStadium->setCity( $stadium['City'] );
$currentStadium->setState( $stadium['State'] );
$currentStadium->setCountry( $stadium['Country'] );
$currentStadium->setCapacity( $stadium['Capacity'] );
$currentStadium->setPlayingSurface( $stadium['PlayingSurface'] );

return $currentStadium;
}
}

关于Symfony2 单元测试服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29841777/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com