Доброго времени суток, коллеги и гости. Некоторое время назад решил я для своего проекта взять за правило написание тестов.
Хотелось поделиться с вами своим опытом подготовки проекта (Symfony 3 + MongoDB) для написания тестов.
Для начала, конечно же, я пошел в документацию от Symfony.
Однако на самом деле я огорчился, так как по этой ссылке неопытный пользователь сможет лишь написать тесты, если он,
грубо говоря, тестирует голый код без использования БД. Мне же, необходимо тестировать функционал своего сервиса, который опирается на данные из БД.
Почитав несколько статей от разных разрабов, собрав множество мнений и методик, я таки пришел к решению.
Шаг первый. Предисловие
Использовать девелоперскую БД мне бы не хотелось, так как мало ли какие действия тесты будут делать с данными(добавит мусора, изменят нужное, и т.д.)
Поэтому в Symfony-проекте пишем:
cp app/config/parameters.yml app/config/parameters_test.yml
И в этом файле мы укажем новые данные доступа к уже другой БД, которая будет использоваться сугубо для тестов
И не забываем файл этот в .gitignore добавить, в репозитории он не нужен.
Шаг второй. Слияние
Теперь надо бы, чтобы этот файл благополучно читался в конфиге, допишем его чтение в app/config/config_test.yml
(В стандартной поставке Symfony у меня этот файл уже был):
app/config/config_test.yml
1234
imports:-{resource:config_dev.yml}-{resource:parameters_test.yml}# именно вот эту строчку добавить надо...
Шаг третий. Кардинальненько
Теперь надо бы, чтобы вся эта конфигурация успешно считывалась для тестов. Для этого достаточно:
1. cp web/app_dev.php web/app_test.php 2. И потом:
web/app_test.php
123456789
// Здесь всякое разное$loader=require__DIR__.'/../app/autoload.php';Debug::enable();// А ты, мой друг, обрати внимание на эту строку, тут грузится ядро с тестовой конфигурацией$kernel=newAppKernel('test',true);$kernel->loadClassCache();$request=Request::createFromGlobals();$response=$kernel->handle($request);// и там еще что то
3. Дайте права на запись кеша для тестового окружения (если необходимо)
4. Дайте права на запись логов для тестового окружения (если необходимо)
Кажется, всё. НО НЕТ!
5. А ваш веб-сервер знает об новом FrontController'e ?
Дам свой рабочий конфиг для связки php-fpm и nginx'a, просто добавьте эту часть в server
Теперь, по идее, можно с браузера зайти на свой локальный адрес проекта http://example.dev/app_test.php/example
А теперь самое интересное - написание тестов.
Не буду останавливаться на стандартных, аля тестирование главной страницы. Пройдусь по работе с БД.
Так как я использую MongoDB, а не MySQL, то мне не пришлось возиться с миграциями, настраивать и синхронизировать схему данных и прочее, все тут оказалось гораздо проще.
Теперь, когда мое тестовое окружение работает с абсолютно другой БД, я могу беспрепятственно делать необходимые мне записи в БД, не боясь поломать данные на дев-машине.
Приведу пример:
<?phpnamespaceTests\SafePageBundle\Controller;useSafePageBundle\Document\Entity\OtherEntity;useSafePageBundle\Document\Entity\Entity;useSymfony\Bundle\FrameworkBundle\Test\WebTestCase;classCheckControllerTestextendsWebTestCase{/** * Ожидаем 200 ответ и дополнительные данные в теле ответа */publicfunctiontestUrlIsOtherEntity(){$client=self::createClient();$this->recordEntity('example.com/page');$client->request('get',$this->getRouter()->generate('api_check'),['url'=>'http://example.com/page?page=1']);$this->assertEquals(200,$client->getResponse()->getStatusCode());$content=$client->getResponse()->getContent();$this->assertJson($content);$json=json_decode($content,true);$this->assertEquals(200,$json['status']);$this->assertEquals(200,$json['data']['code']);}/** * Ожидаем 200 ответ и дополнительные данные в теле ответа */publicfunctiontestUrlIsEntity(){$client=self::createClient();$this->recordOtherEntity('example.com');$client->request('get',$this->getRouter()->generate('api_check'),['url'=>'http://example.com/page?page=1']);$this->assertEquals(200,$client->getResponse()->getStatusCode());$content=$client->getResponse()->getContent();$this->assertJson($content);$json=json_decode($content,true);$this->assertEquals(200,$json['data']['code']);}/** * Очистка БД после каждого теста */publicfunctiontearDown(){$this->removeAllCollection(Entity::class);$this->removeAllCollection(OtherEntity::class);}/** * Получение DocumentManager'a * @return \Doctrine\Common\Persistence\ObjectManager|object */publicfunctiongetDM(){$client=self::createClient();return$client->getContainer()->get('doctrine_mongodb')->getManager();}/** * Очищает всю коллекцию * @param string $collection Класс модели для очистки */publicfunctionremoveAllCollection($collection){$dm=$this->getDM();$entities=$dm->getRepository($collection)->findAll();foreach($entitiesas$entity){$dm->remove($entity);}$dm->flush();}/** * Получение Роутера * @return \Symfony\Bundle\FrameworkBundle\Routing\Router */publicfunctiongetRouter(){$client=self::createClient();return$client->getContainer()->get('router');}/** * Получение параметров из конфигурации * * @param $parameter * @return string | integer | null */publicfunctiongetParam($parameter){$client=self::createClient();return$client->getContainer()->getParameter($parameter);}/** * Получение клиента, который может авторизоваться через Basic Authentication * @return \Symfony\Bundle\FrameworkBundle\Client */publicfunctiongetAuthClient(){$client=static::createClient(array(),array('PHP_AUTH_USER'=>$this->getParam('admin_login'),'PHP_AUTH_PW'=>$this->getParam('admin_password'),));return$client;}/** Запись такая же, как в обычных контроллерах */publicfunctionrecordEntity($page,$level='',\DateTime$created=null){$entity=newEntity();if(!$created||$created=new\DateTime()){$entity->setCreatedAt($created);}$entity->setLevel($level);$entity->setPage($page);$dm=$this->getDM();$dm->persist($entity);$dm->flush();}publicfunctionrecordOtherEntity($domain,$level='',\DateTime$created=null){$otherEntity=newOtherEntity();if(!$created||$created=new\DateTime()){$otherEntity->setCreatedAt($created);}$otherEntity->setLevel($level);$otherEntity->setDomain($domain);$dm=$this->getDM();$dm->persist($otherEntity);$dm->flush();}}
Как можно заметить, у нашего $client есть доступ в контейнер сервисов, и мы можем через доктрину аналогично контроллерам писать в БД, которая отделена от девелоперской.
Кроме этого, мы ее еще и затирать можем без боязни (если с умом подходить к вопросу).
К слову сказать, тесты у меня запускаются просто: phpunit -c ./
Что в итоге?
В итоге мы имеем проект, для которого можно писать тесты, обособленно, в новом тестовом окружении.
Данный в статье код предназначен для ознакомления относительно сабжа, я дал облегченную версию, доступную для понимания.
Без сомнения, могут быть какие то недочеты в данном опыте, я лишь постарался помочь ищущим, каким был и я. Спасибо за внимание! Спишемся.