我有一个WebTestCase,它在我的应用程序中执行一些基本路由。
我想在PHPUnit的setUp
方法中创建一个与我的主数据库相同的测试数据库,并将数据加载到其中。
我正在做一些解决方法并执行一些控制台命令,如下所示:
class FixturesWebTestCase extends WebTestCase
{
protected static $application;
protected function setUp()
{
self::runCommand('doctrine:database:create');
self::runCommand('doctrine:schema:update --force');
self::runCommand('doctrine:fixtures:load --purge-with-truncate');
}
protected static function runCommand($command)
{
$command = sprintf('%s --quiet', $command);
return self::getApplication()->run(new StringInput($command));
}
protected static function getApplication()
{
if (null === self::$application) {
$client = static::createClient();
self::$application = new Application($client->getKernel());
self::$application->setAutoExit(false);
}
return self::$application;
}
}
但我确信这不是最好的方法,特别是因为doctrine:fixtures:load
期望用户点击Y
字符来确认操作。
我该如何解决?
答案 0 :(得分:34)
为了绕过用户确认,您可以使用
doctrine:fixtures:load --no-interaction
or
doctrine:fixtures:load -n
答案 1 :(得分:33)
如果您想使用doctrine:fixtures:load
,可以使用--append
选项来避免用户确认。由于每次都在重新创建数据库,因此不需要进行清除。我曾经单独使用学说装置进行测试,但后来转而使用灯具和放大器。 LiipFunctionalTestBundle以避免干旱。该捆绑包使夹具更易于管理。
编辑:David Jacquel的答案是加载Doctrine Fixtures的正确答案:
doctrine:fixtures:load --no-interaction
or
doctrine:fixtures:load -n
答案 2 :(得分:31)
您可以为测试用例创建一个基类,通过利用Doctrine Data Fixtures库中的某些类,可以轻松加载夹具。这个类看起来很像这样:
<?php
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
abstract class FixtureAwareTestCase extends KernelTestCase
{
/**
* @var ORMExecutor
*/
private $fixtureExecutor;
/**
* @var ContainerAwareLoader
*/
private $fixtureLoader;
public function setUp()
{
self::bootKernel();
}
/**
* Adds a new fixture to be loaded.
*
* @param FixtureInterface $fixture
*/
protected function addFixture(FixtureInterface $fixture)
{
$this->getFixtureLoader()->addFixture($fixture);
}
/**
* Executes all the fixtures that have been loaded so far.
*/
protected function executeFixtures()
{
$this->getFixtureExecutor()->execute($this->getFixtureLoader()->getFixtures());
}
/**
* @return ORMExecutor
*/
private function getFixtureExecutor()
{
if (!$this->fixtureExecutor) {
/** @var \Doctrine\ORM\EntityManager $entityManager */
$entityManager = self::$kernel->getContainer()->get('doctrine')->getManager();
$this->fixtureExecutor = new ORMExecutor($entityManager, new ORMPurger($entityManager));
}
return $this->fixtureExecutor;
}
/**
* @return ContainerAwareLoader
*/
private function getFixtureLoader()
{
if (!$this->fixtureLoader) {
$this->fixtureLoader = new ContainerAwareLoader(self::$kernel->getContainer());
}
return $this->fixtureLoader;
}
}
然后,在您的测试用例中,只需扩展上面的类,然后在测试之前添加所有需要的灯具并执行它们。这将在加载灯具之前自动清除数据库。示例如下:
class MyTestCase extends FixtureAwareTestCase
{
public function setUp()
{
parent::setUp();
// Base fixture for all tests
$this->addFixture(new FirstFixture());
$this->addFixture(new SecondFixture());
$this->executeFixtures();
// Fixtures are now loaded in a clean DB. Yay!
}
}
(我决定“弃用”这个答案,因为它只解释了如何清理数据库而不告诉如何在之后加载灯具)。
有一种更简洁的方法来完成此操作而无需运行命令。它基本上包括使用SchemaTool和ORMPurger的组合。您可以创建一个抽象基类来执行此类操作,以避免为每个专门的测试用例重复它们。下面是一个测试用例类的代码示例,它为通用测试用例设置数据库:
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\Tools\SchemaTool;
abstract class DatabaseAwareWebTestCase extends WebTestCase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
$kernel = static::createKernel();
$kernel->boot();
$em = $kernel->getContainer()->get('doctrine')->getManager();
$schemaTool = new SchemaTool($em);
$metadata = $em->getMetadataFactory()->getAllMetadata();
// Drop and recreate tables for all entities
$schemaTool->dropSchema($metadata);
$schemaTool->createSchema($metadata);
}
protected function tearDown() {
parent::tearDown();
$purger = new ORMPurger($this->getContainer()->get('doctrine')->getManager());
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
$purger->purge();
}
}
这样,在运行继承自上述类的每个测试用例之前,将从头开始重建数据库模式,然后在每次测试运行后进行清理。
希望这有帮助。
答案 3 :(得分:5)
我偶然发现了一个名为Doctrine-Test-Bundle的非常整洁的软件包 它不是在每个测试中创建和删除模式,而是简单地回滚。 我的测试从1分40秒到2分钟。它是孤立的。 您所需要的只是一个清晰的测试数据库,它可以解决问题。
答案 4 :(得分:2)
我使用了这个命令:
yes | php app/console doctrine:fixtures:load --purge-with-truncate
但是LiipFunctionalTestBundle当然看起来很有希望。
答案 5 :(得分:0)
我想加载像Sample Code:
jsp code:
<div id="cycle-slideshow" style="width: 100%;">
<div class="container-fluid animated" id="container_<%=i%>" style="opacity: 0.01;width: 100%;">
<div class="row">
<div class="container-fluid" id="container">
<div class="row border-bottom">
<nav class="navbar navbar-static-top white-bg main_header" role="navigation" style="margin-bottom: 0">
<div class="header" id="header">
<div class="col-lg-12" style="max-height:25px">
<div class="col-lg-6">
<h2 >
<s:property value="name" escape="false"/>
</h2>
</div>
<div class="col-lg-6">
<h4 class="pull-right push-bottom" style="margin-top:25px;">
<span class="timeData" style="text-align:center;"></span>
</h4>
</div>
</div>
</div>
</nav>
</div>
<!-------------------header end------------------------>
<!-------------------------graph and stream area---------------------------->
<!----------------------left panel start-------------------->
<div class="wrapper wrapper-content" style="padding: 6px 5px 20px !important;">
<div class="row">
<div class="col-lg-9" id="graph_area">
<div class="row_custom">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins" style="margin-bottom:14px;">
<div class="ibox-title graph_header">
<h5><s:property value="settings[1].elementName" escape="false"/></h5>
</div>
<div class="ibox-content" style="padding-bottom:4px;">
<div class="row">
<div class="col-lg-12" style="padding:4px;" id="<s:property value="settings[1].elementId"/>_<s:property value="settings[1].id"/>_<s:property value="id"/>" >
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" >
<div class="col-lg-6">
<div class="row" style="margin-left:1px!important;">
<div class="ibox float-e-margins">
<div class="ibox-title graph_header">
<h5><s:property value="settings[3].elementName"/></h5>
</div>
<div class="ibox-content" style="padding-bottom:4px;">
<div class="row" >
<div class="col-lg-12" id="<s:property value="settings[3].elementId"/>_<s:property value="settings[3].id"/>_<s:property value="id"/>" style="display:inline-block;">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title graph_header">
<h5><s:property value="settings[4].elementName"/></h5>
</div>
<div class="ibox-content" style="padding-bottom:14px;">
<div class="row">
<div class="col-lg-12" style="padding:4px;display:inline-block;" id="<s:property value="settings[4].elementId"/>_<s:property value="settings[4].id"/>_<s:property value="id"/>" >
//area for map
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!----------------------left panel End-------------------->
<!----------------------right panel start-------------------->
<div class="col-lg-3" id="data_stream_div" >
<div class="row">
<div class="ibox float-e-margins">
<div class="ibox-title graph_header">
<h5><s:property value="settings[2].elementName"/></h5>
</div>
<div class="ibox-content" id="temp_1_stream" style="padding-left:1px!important;">
<div>
<div class="feed-activity-list" style="padding:4px;" id="<s:property value="settings[2].elementId"/>_<s:property value="settings[2].id"/>_<s:property value="id"/>" >
//area for table
</div>
</div>
</div>
</div>
</div>
</div>
<!----------------------right panel End---------------------->
</div>
</div>
<!---------------------graph area------------------>
</div>
</div>
</div>
<div class="container-fluid animated" id="container_<%=i%>" style="opacity: 0.01;width: 100%;">
<div class="row">
<div class="container-fluid" id="container">
<div class="row border-bottom">
<nav class="navbar navbar-static-top white-bg main_header" role="navigation" style="margin-bottom: 0">
<div class="header" id="header">
<div class="col-lg-12" style="max-height:25px">
<div class="col-lg-6">
<h2 >
<s:property value="name" escape="false"/>
</h2>
</div>
<div class="col-lg-6">
<h4 class="pull-right push-bottom" style="margin-top:25px;">
<span class="timeData" style="text-align:center;"></span>
</h4>
</div>
</div>
</div>
</nav>
</div>
<!-------------------header end------------------------>
<!-------------------------graph and stream area---------------------------->
<!----------------------left panel start-------------------->
<div class="wrapper wrapper-content" style="padding: 6px 5px 20px !important;">
<div class="row">
<div class="col-lg-9" id="graph_area">
<div class="row_custom">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins" style="margin-bottom:14px;">
<div class="ibox-title graph_header">
<h5><s:property value="settings[1].elementName" escape="false"/></h5>
</div>
<div class="ibox-content" style="padding-bottom:4px;">
<div class="row">
<div class="col-lg-12" style="padding:4px;" id="<s:property value="settings[1].elementId"/>_<s:property value="settings[1].id"/>_<s:property value="id"/>" >
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" >
<div class="col-lg-6">
<div class="row" style="margin-left:1px!important;">
<div class="ibox float-e-margins">
<div class="ibox-title graph_header">
<h5><s:property value="settings[3].elementName"/></h5>
</div>
<div class="ibox-content" style="padding-bottom:4px;">
<div class="row" >
<div class="col-lg-12" id="<s:property value="settings[3].elementId"/>_<s:property value="settings[3].id"/>_<s:property value="id"/>" style="display:inline-block;">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title graph_header">
<h5><s:property value="settings[4].elementName"/></h5>
</div>
<div class="ibox-content" style="padding-bottom:14px;">
<div class="row">
<div class="col-lg-12" style="padding:4px;display:inline-block;" id="<s:property value="settings[4].elementId"/>_<s:property value="settings[4].id"/>_<s:property value="id"/>" >
//area for piechart
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!----------------------left panel End-------------------->
<!----------------------right panel start-------------------->
<div class="col-lg-3" id="data_stream_div" >
<div class="row">
<div class="ibox float-e-margins">
<div class="ibox-title graph_header">
<h5><s:property value="settings[2].elementName"/></h5>
</div>
<div class="ibox-content" id="temp_1_stream" style="padding-left:1px!important;">
<div>
<div class="feed-activity-list" style="padding:4px;" id="<s:property value="settings[2].elementId"/>_<s:property value="settings[2].id"/>_<s:property value="id"/>" >
//area for table
</div>
</div>
</div>
</div>
</div>
</div>
<!----------------------right panel End---------------------->
</div>
</div>
<!---------------------graph area------------------>
</div>
</div>
</div>
</div>
code for slider:
function getslider(){
$('#cycle-slideshow').cycle({
timeout: 90000,
slides: '> div',
fx:'fade'
});
$( '#cycle-slideshow' ).on( 'cycle-after', function( event, opts ) {
console.log("lastRunTime "+lastRunTime);
console.log("timedifference "+((lastRunTime-event.timeStamp)/1000));
lastRunTime=event.timeStamp;
});
}
code for piechart:
function getShareOfVoice() {
$.ajax({
type:"POST",
dataType:"json",
url:'<s:url value="XXXXXX',
data:{sampledata : sampledate},
cache: false,
async:"false",
success:function(data) {
console.log("in success for share of voice");
var vals = JSON.parse(JSON.stringify(data));
if(vals.data.length>0){
var dataArray = vals.data;
var overallseries = [];
overallShareOfVoiceOptions.chart.renderTo = divid;
for ( var pos = 0; pos < dataArray.length; pos++) {
var d = dataArray[pos];
var dd = {};
var legend_icon = " ";
if (d.pic != null
&& typeof d.pic !== "undefined"
&& d.pic.length > 0) {
legend_icon = '<img style="width: 45px; height: 30px" src="'+d.pic+'" >';
} else {
legend_icon = d.name;
}
dd.color = d.cssStyle;
dd.y = d.count;
dd.name = legend_icon;
overallseries.push(dd);
}
overallShareOfVoiceOptions.series[0].data = overallseries;
if (overallseries.length > 0) {
var shareOfVoiceOverallGraph = new Highcharts.Chart(
overallShareOfVoiceOptions);
}
if (draw !== true) {
progressbar(divid);
}
setTimeout(function() {
getShareOfVoice();
}, 5 * 60 * 1000);
}
},error:function(data){
setTimeout(function() {
getShareOfVoice();
}, 2 * 60 * 1000);
}
});
}
code for highMap:
function getRegionChart() {
$.ajax({
type:"POST",
url:'XXXXXXXXXXXXXXXX',
dataType:'json',
data : {sampledata : sampledata},
success : function(data){
var regions = JSON.parse(JSON.stringify(data));
if(regions.length > 0){
$('#' + divid).highcharts('Map',{
title : {
text : null
},
subtitle : {
text : null
},
mapNavigation : {
enabled : false,
buttonOptions : {
verticalAlign : 'bottom'
}
},
legend : {
align : 'left',
layout : 'vertical',
verticalAlign : 'middle',
y : 10,
floating : true,
borderWidth : 0,
backgroundColor : 'white',
},
colorAxis : {
min : 0,
minColor : '#cccc00',
maxColor : '#666600'
},
navigation : {
menuItemStyle : {
fontSize : '10px'
},
buttonOptions : {
enabled : false
}
},
credits : {
enabled : false
},
series : [ {
data : regions,
mapData : Highcharts.maps['countries/in/custom/in-all-disputed'],
joinBy : 'hc-key',
name : 'State',
states : {
hover : {
color : '#BADA55'
}
},
dataLabels : {
enabled : true,
formatter : function() {
if(!(this.point.options["show"]))
return;
return this.point.value+ "%";
}
}
} ]
});
setTimeout(function() {
getRegionChart();
}, 5*60 *1000);
}
},error : function(data) {
setTimeout(function() {
getRegionChart();
},2*60 * 1000);
}
});
}
命令那样的所有灯具。我不想从测试用例中运行doctrine:fixtures:load
,因为它似乎是一种乱糟糟的做事方式。我看了一下doctrine命令是如何做到的,只是复制了相关的行。
我从Symfony exec
扩展而且在创建内核之后,我只调用了我的方法,它的工作方式与Doctrine load-fixtures命令完全相同。
WebTestCase
答案 6 :(得分:0)
最近,捆绑软件hautelook/AliceBundle暴露了两个特征,可帮助您解决功能测试中加载夹具的用例:RefreshDatabaseTrait
和ReloadDatabaseTrait
。
来自文档:
namespace App\Tests;
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class NewsTest extends WebTestCase
{
use RefreshDatabaseTrait;
public function postCommentTest()
{
$client = static::createClient(); // The transaction starts just after the boot of the Symfony kernel
$crawler = $client->request('GET', '/my-news');
$form = $crawler->filter('#post-comment')->form(['new-comment' => 'Symfony is so cool!']);
$client->submit($form);
// At the end of this test, the transaction will be rolled back (even if the test fails)
}
}
你很好!