Створення блогу на Symfony 2.8 lts [ Частина 6]




Навігація по частинах керівництва
Частина 1 — Конфігурація Symfony2 і шаблонів
Частина 2 — Сторінка з контактною інформацією: валідатори, форми та електронна пошта
Частина 3 — Doctrine 2 і Фікстури даних
Частина 4 — Модель коментарів, Репозиторій та Міграції Doctrine 2
Частина 5 — Twig розширення, Бічна панель(sidebar) і Assetic


Проект на Github
Дізнатися як встановити потрібну вам частину керівництва, можна в описі до дерева з посилання. (Наприклад, якщо ви хочете почати це з уроку не проходячи попередній)


Тестування в Symfony2

PHPUnit став де-факто стандартом для написання тестів в PHP, так що його вивчення принесе вам користь у всіх ваших PHP проектах. Давайте також не забувати, що більшість тем, що розглядаються в даній частині, що не залежать від мови, і тому можуть бути застосовані до інших мов, які ви використовуєте.

Підказка

Якщо ви плануєте писати свої власні Open Source бандли Symfony2, у вас набагато більше шансів отримати хороший відгук від користувачів, якщо Ваш бандл добре протестований (і задокументований). Подивіться на список існуючих Symfony2 бандлів, доступних на knpbundles.


Модульне (unit) тестування

Модульне тестування перевіряє правильну роботу окремих одиниць функції при використанні в ізоляції. В об'єктно-орієнтованому коді як в Symfony2, модуль це — клас і його методи. Наприклад, ми могли б писати тести для класів сутностей Blog і Comment. При написанні модульних тестів, тести повинні бути написані незалежно від інших тестів, тобто, результат тесту Б не повинен залежати від результату А. Модульне тестування дає можливість створювати фіктивні об'єкти, які дозволяють тестувати функції мають зовнішні залежності. Фіктивні об'єкти(Моки) дозволяють моделювати виклик функції замість фактичного виконання. Прикладом цього може бути модульне тестування класу, який обертає зовнішній API. Клас API може використовувати протокол транспортного рівня для здійснення зв'язку з зовнішнім API. Ми могли б зробити фіктивний метод запиту транспортного рівня, щоб повернути результати, які ми поставили, а не отримувати насправді повернулися з зовнішнього API. Модульне тестування не перевіряє, правильно функціонують компоненти програми разом, це показано в наступному розділі.

Функціональне тестування

Функціональне тестування перевіряє інтеграцію різних компонентів програми, такі як маршрутизація, контролери і відображення. Функціональні тести аналогічні ручним тестів, які ви могли б зробити у себе в браузері, наприклад, запит головної сторінки, натиснувши на посилання запису і перевірка правильності її відображення. Функціональне тестування дає вам можливість автоматизувати цей процес. Symfony2 поставляється в комплекті з цілим рядом корисних класів, які допомагають при функціональному тестуванні включаючи Client, який здатний робити запити на сторінки, відправляти форми і DOM Crawler , який ми можемо використовувати для обходу Відповіді (Response) від клієнта.

PHPUnit

Як було зазначено вище, Symfony2 тести написані з використанням PHPUnit. Вам необхідно буде встановити PHPUnit для того, щоб запустити тести з цієї частини. Для отримання більш детальної інформації по установці зверніться до офіційної документації на веб-сайті PHPUnit. Для запуску тестів в Symfony2 вам потрібно встановити PHPUnit 5.4 (потрібно PHP 5.6). PHPUnit дуже велика бібліотека тестування, у зв'язку з цим будуть зроблені посилання на офіційну документацію, де може знадобитися додаткова інформація.

Затвердження (assertions)

Написання тестів є перевіркою того, дорівнює чи очікуваний результат тесту, фактичного результату. Існує ряд методів тверджень у PHPUnit, щоб допомогти нам з цим завданням. Деякі з поширених методів тверджень, які ми будемо використовувати, перераховані нижче.

// Check 1 === 1 is true
$this->assertTrue(1 === 1);

// Check 1 === 2 is false
$this->assertFalse(1 === 2);

// Check 'Hello' дорівнює 'Hello'
$this->assertEquals('Hello', 'Hello');

// Check array has key 'language'
$this->assertArrayHasKey('language', array('language' => 'php', 'size' => '1024'));

// Check array contains value 'php'
$this->assertContains('php', array('php', 'рубін', 'c++', 'JavaScript'));



Повний список тверджень(assertions) доступний в документації PHPUnit

Запуск тестів Symfony2

Перед тим, як приступити до написання декількох тестів, давайте подивимося на те, як ми запускаємо тести в Symfony2. РНРUnit може використовувати файл конфігурації. У нашому проекті Symfony2 цей файл знаходиться app/phpunit.xml.dist. Так як цей файл з суфіксом.dist, вам необхідно скопіювати його вміст у файл з ім'ямapp/phpunit.xml.

Підказка

Якщо ви використовуєте VCS, таку як Git, ви повинні додати файл app/phpunit.xml в список ігнорування.


Якщо ви подивитеся на вміст файлу конфігурації PHPUnit ви побачите наступне.

<!-- app/phpunit.xml -->

<testsuites>
<testsuite name="Project Test Suite">
<fs>../src/*/*Bundle/Tests</directory>
<fs>../src/*/Bundle/*Bundle/Tests</directory>
</testsuite>
</testsuites>


Ці параметри налаштовують деякі каталоги, які є частиною нашого тестового набору. При запуску PHPUnit буде шукати тести для запуску у зазначених каталогах. Ви також можете передати додаткові аргументи командного рядка для запуску тестів PHPUnit в певних каталогах. Ви побачите, як домогтися цього пізніше.

Підказка

Для отримання додаткової інформації про налаштування PHPUnit за допомогою XML-файлу дивіться документаціюPHPUnit.


Запуск поточних тестів

Так як ми використовували одну з команд Symfony2 генератора для створення Blogger BlogBundle в першій частині, вона також створила тест контролера для класу Default Controller. Ми можемо виконати цей тест, виконавши наступну команду з кореневого каталогу проекту. -c опція вказує, що PHPUnit повинен завантажити конфігурацію з каталогу app.
$ phpunit -c app


Після того, як тестування завершиться, ви повинні бути повідомлені про те, що тест не вдався. Якщо ви подивитеся на клас DefaultControllerTest, розташованого src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php ви побачите
наступний зміст
<?php

namespace Blogger\BlogBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();

$crawler = $client->request('GET', '/');

$this->assertContains('Hello World', $client->getResponse()->getContent());
}
}



Це функціональний тест для класу Default Controller, який згенерував Symfony2. Якщо ви пам'ятаєте в частині 1, у цього контролера метод, який обробляв запит, за цією адресою у нас виводився шаблон з вмістом «Hello World». Так як ми видалили цей контролер і у шаблону по цьому маршруту зовсім інший зміст, вищезгаданий тест терпить невдачу.
Функціональне тестування є великою частиною цієї глави і буде розглянуто в деталях пізніше.
Так як був видалений клас Default Controller, ви можете видалити файл src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php

Модульне тестування

Як пояснювалося раніше, модульне тестування стосується тестування окремих одиниць вашого застосування в ізоляції. При написанні модульних тестів рекомендується дублювати структуру пакету в папці тестів. Наприклад, якщо ви хочете, протестувати сутність класу Blog, розташовану вsrc/Blogger/BlogBundle/Entity/Blog.php тестовий файл буде перебувати в src/Blogger/BlogBundle/Tests/Entity/BlogTest.php. Приклад буде виглядати
наступним чином
src/Blogger/BlogBundle/
Entity/
Blog.php
Comment.php
Controller/
PageController.php
Twig/
Extensions/
BloggerBlogExtension.php
Tests/
Entity/
BlogTest.php
CommentTest.php
Controller/
PageControllerTest.php
Twig/
Extensions/
BloggerBlogExtensionTest.php



Зверніть увагу на те, що кожен з тестових файлів має суфікс Test.

Тестування сутності Blog — метод slugify

Ми почнемо тестування методу slugify в сутності Blog. Давайте напишемо декілька тестів, щоб переконатися, що цей метод працює правильно. Створіть новий файл, розташований в
src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
<?php
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php

namespace Blogger\BlogBundle\Tests\Entity;

use Blogger\BlogBundle\Entity\Blog;

class BlogTest extends \PHPUnit_Framework_TestCase
{

}



Ми створили тестовий клас для сутності Blog. Зверніть увагу, що розташування файлу відповідає структурі папок, згаданої вище. Клас Blog Test розширює базовий клас PHPUnit PHPUnit_Framework_TestCase. Всі тести, які Ви пишете для PHPUnit будуть нащадками цього класу. Ви пам'ятаєте з попередніх частин, що\ повинен бути поміщений перед ім'ям класу PHPUnit_Framework_TestCase так як клас оголошений в загальному просторі імен PHP.

Тепер у нас є кістяк класу для тестів сутності Blog, давайте напишемо тест. Тести в PHPUnit є методами класу Test з префіксом test, наприклад testSlugify(). Оновіть BlogTest, розташований
src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php

// ..

class BlogTest extends \PHPUnit_Framework_TestCase
{
public function testSlugify()
{
$blog = new Blog();

$this->assertEquals('hello-world', $blog->slugify('Hello World'));
}
}



Це дуже простий тест. Він створює нову сутність Blog і запускаєassertEquals() для результату методу slugify. Метод assertEquals() приймає 2 обов'язкових аргументу, очікуваний результат і фактичний результат. Може бути переданий необов'язковий третій аргумент, щоб вивести повідомлення, коли тест терпить невдачу.

Давайте запустимо наш новий unit тест. Введіть наступну команду в консолі.

$ phpunit -c app


Ви повинні побачити наступне.

PHPUnit 5.4.6 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 348 ms, Memory: 13.25 MB

OK (test 1, 1 assertion)


Висновок РНРunit дуже простий, спочатку, показується деяка інформація про PHPUnit і кількість кожного запущеного тесту, в нашому випадку ми маємо лише 1 тест. В кінці він інформує нас про результати тесту. Для нашого BlogTest ми запустили тільки 1 тест з 1 твердженням. Якщо у вас налаштований висновок квітів у вашій командному рядку ви побачите, що останній рядок замальовано зеленим світлом, показуючи, що все в порядку OK. Давайте поновимо метод testSlugify() щоб подивитися, що станеться, коли тест зазнає невдачі.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php

// ..

public function testSlugify()
{
$blog = new Blog();

$this->assertEquals('hello-world', $blog->slugify('Hello World'));
$this->assertEquals('a day with symfony2', $blog->slugify('A Day With Symfony2'));
}


Перезапустіть модульний тест як раніше. Буде виведено
наступне
PHPUnit 5.4.6 by Sebastian Bergmann and contributors.

F 1 / 1 (100%)

Time: 340 ms, Memory: 13.25 MB

There was 1 failure:

1) Blogger\BlogBundle\Tests\Entity\BlogTest::testSlugify
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'a day with symfony2'
+'a-day-with-symfony2'

D:\local\symfony-blog\src\Blogger\BlogBundle\Tests\Entity\BlogTest.php:15

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.



Висновок трохи більше в цей раз. Символ F говорить нам про те, що тест не пройдено. Ви також побачите символ E якщо ваш тест буде містити помилки. Далі РНРUnit повідомляє нас про деталі невдач, в даному випадку одного. Ми можемо бачити що метод Blogger\BlogBundle\Tests\Entity\BlogTest::testSlugify не вдався, оскільки очікувані та фактичні значення різні. Якщо у вас є висновок квітів у вашій командному рядку ви побачите, що останній рядок замальовано червоним кольором, показуючи, що в тесті є помилки. Виправте методtestSlugify() так, щоб він був успішно виконаний.

// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php

// ..

public function testSlugify()
{
$blog = new Blog();

$this->assertEquals('hello-world', $blog->slugify('Hello World'));
$this->assertEquals('a-day-with-symfony2', $blog->slugify('A Day With Symfony2'));
}


Перед тим як піти далі додайте ще кілька тестів
для методу slugify()
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php

// ..

public function testSlugify()
{
$blog = new Blog();

$this->assertEquals('hello-world', $blog->slugify('Hello World'));
$this->assertEquals('a-day-with-symfony2', $blog->slugify('A Day With Symfony2'));
$this->assertEquals('hello-world', $blog->slugify('Hello world'));
$this->assertEquals('symblog', $blog->slugify('symblog '));
$this->assertEquals('symblog', $blog->slugify(' symblog'));
}



Тепер, коли ми протестували метод slugify, ми повинні переконатися, що Blog $slug правильно встановлений, коли оновлюється Blog $title. Додайте наступні методи в файл
src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php

// ..

public function testSetSlug()
{
$blog = new Blog();

$blog->setSlug('Symfony2 Blog');
$this->assertEquals('symfony2-blog', $blog->getSlug());
}

public function testSetTitle()
{
$blog = new Blog();

$blog->setTitle('Hello World');
$this->assertEquals('hello-world', $blog->getSlug());
}



Ми почали з тестування методу setSlug, щоб переконатися, що елемент $slug правильно додає slug при оновленні. Далі ми перевіряємо, що $slug правильно оновлюється, коли метод setTitle викликається сутністю Blog.
Запустіть тести для перевірки працездатності сутності Blog.

Тестування розширення Twig

У попередній частині ми створили розширення Twig для перетворення \DateTime в рядок, щоб визначити час, що минув з моменту публікації коментаря. Створіть новий файл
src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php
<?php
// src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php

namespace Blogger\BlogBundle\Tests\Twig\Extensions;

use Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtension;

class BloggerBlogExtensionTest extends \PHPUnit_Framework_TestCase
{
public function testCreatedAgo()
{
$blog = new BloggerBlogExtension();

$this->assertEquals("0 seconds ago", $blog->createdAgo(new \DateTime()));
$this->assertEquals("34 seconds ago", $blog->createdAgo($this->getDateTime(-34)));
$this->assertEquals("1 minute ago", $blog->createdAgo($this->getDateTime(-60)));
$this->assertEquals("2 minutes ago", $blog->createdAgo($this->getDateTime(-120)));
$this->assertEquals("1 hour ago", $blog->createdAgo($this->getDateTime(-3600)));
$this->assertEquals("1 hour ago", $blog->createdAgo($this->getDateTime(-3601)));
$this->assertEquals("2 hours ago", $blog->createdAgo($this->getDateTime(-7200)));

// Cannot create time in the future
$this->setExpectedException('\InvalidArgumentException');
$blog->createdAgo($this->getDateTime(60));
}

protected function getDateTime($delta)
{
return new \DateTime(date"Y-m-d H:i:s", time()+$delta));
}
}



Клас налаштований так само, як і раніше, створюючи метод testCreatedAgo() для тестування розширення Twig. Ми ввели ще один метод PHPUnit в цьому тесті setExpectedException(). Ми знаємо, що метод createdAgo розширення Twig не може обробляти дати в майбутньому і буде переданий в \Exception. метод getDateTime() допоміжний, для створення екземпляра \DateTime. Зверніть увагу на те, що немає префікса test, так РНРUnit не буде намагатися виконати її в якості тесту. Відкрийте консоль і запустіть тести для цього файлу. Ми могли б просто запустити тест, як і раніше, але ми можемо також вказати PHPUnit виконувати тести тільки для певної папки (і вкладених папок) або файлу. Введіть наступну команду:

$ phpunit -c app src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php


Це запустить тести тільки для файлу BloggerBlogExtensionTest. PHPUnit повідомить нам, що тест не вдався. Висновок показаний нижче.
1) Blogger\BlogBundle\Tests\Twig\Extensions\BloggerBlogExtensionTest::testCreatedAgo
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'0 seconds ago'
+'0 second ago'
//..


Ми очікували, що перше твердження повернеться 0 секунд назад, але воно не повернулося так як друге слово не було множини. Давайте поновимо розширення Twig щоб виправити це
src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php
<?php
// src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php

namespace Blogger\BlogBundle\Twig\Extensions;

class BloggerBlogExtension extends \Twig_Extension
{
// ..

public function createdAgo(\DateTime $dateTime)
{
// ..
if ($delta < 60)
{
// Seconds
$time = $delta;
$duration = $time . " second" . (($time === 0 || $time > 1) ? "s" : "") . " ago";
}
// ..
}

// ..
}



Перезапустіть Unit тести. Ви повинні побачити, що перше твердження відображається правильно, але тест досі не вдається. Давайте розглянемо наступний висновок.

1) Blogger\BlogBundle\Tests\Twig\Extensions\BloggerBlogExtensionTest::testCreatedAgo
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'1 hour ago'
+'60 minutes ago'


Ми бачимо, що не вдається п'яте твердження. Дивлячись на тест, ми можемо бачити, що Розширення Twig функціонувало неправильно. Повинно було бути повернуто 1 годину назад, але замість цього повернуто 60 хвилин тому. Якщо ми розглянемо код в BloggerBlogExtension Twig ми побачимо причину. Ми порівнюємо час, який може бути включено, тобто, ми використовуємо <= замість <. Оновіть розширення Twig щоб це виправити
src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php
<?php
// src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php

namespace Blogger\BlogBundle\Twig\Extensions;

class BloggerBlogExtension extends \Twig_Extension
{
// ..

public function createdAgo(\DateTime $dateTime)
{
// ..

else if ($delta < 3600)
{
// Mins
$time = floor($delta / 60);
$duration = $time . " minute" . (($time > 1) ? "s" : "") . " ago";
}
else if ($delta < 86400)
{
// Hours
$time = floor($delta / 3600);
$duration = $time . " hour" . (($time > 1) ? "s" : "") . " ago";
}

// ..
}

// ..
}



Тепер знову запустимо всі тести з допомогою наступної команди.

$ phpunit -c app


Це запустить всі наші тести і покаже, що всі тести успішно пройдені. Хоча ми написали тільки невелика кількість модульних тестів ви повинні отримати відчуття того, наскільки потужним і важливим є тестування при написанні коду. Тестування також допомагає додавати будь-які майбутні функціональні можливості до проекту, не ламаючи попередні характеристики. На цьому ми завершимо розгляд модульного тестування.

Підказка

Спробуйте додати ваші власні модульні тести.


Функціональне тестування

Ми написали кілька модульних тестів, давайте перейдемо до тестування декількох компонентів разом. Перший розділ функціонального тестування буде включати в себе імітацію запитів браузера для тестування згенерованих відповідей.

Тестування сторінки About
Ми почнемо з тестування класу PageController для сторінки About. Так як сторінка є дуже простий, це гарне місце для початку. Створіть новий файл
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
<?php
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

namespace Blogger\BlogBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class PageControllerTest extends WebTestCase
{
public function testAbout()
{
$client = static::createClient();

$crawler = $client->request('GET', '/about');

$this->assertEquals(1, $crawler->filter('h1:contains("About symblog")')->count());
}
}



Ми побачимо, що тест контролера дуже схожий на клас DefaultControllerTest. Тестування сторінки about, перевіряє рядок About symblog яка присутня у створеному HTML, а саме всередині тега H1. Клас PageControllerTest не розширює \PHPUnit_Framework_TestCase, як ми бачили в прикладі з модульним тестуванням, замість цього він розширює клас WebTestCase. Цей клас є частиною Symfony2 FrameworkBundle.

Як було пояснено вище тестові класи PHPUnit повинні розширювати \PHPUnit_Framework_TestCase але, коли додаткові або загальні функціональні можливості потрібні в декількох тестах корисно инкапсулировать це в своєму власному класі і зробити так щоб ваш тестові класи розширювали його. WebTestCase робить саме це, він надає кілька корисних методів для виконання функціональних тестів у Symfony2. Подивіться на файл WebTestCase, розташованого vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php, ви побачите, що цей клас є розширенням класу KernelTestCase.

// vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php

abstract class WebTestCase extends KernelTestCase
{
// ..
}


Якщо ви подивитеся на метод createClient() у класі WebTestCase ви можете побачити, що він створює екземпляр Symfony2 Kernel. Дотримуючись методів далі ви також помітите, що середовище встановлено у test (якщо не перевизначено у якості одного з аргументів createClient()). Це тестове оточення про який ми говорили в попередній частині.

Подивившись на наш тестовий клас ми можемо бачити, що для отримання і запуску тесту викликається метод createClient(). Потім ми викликаємо метод request() на стороні клієнта для імітації GET HTTP-запиту браузера URL /about (це буде точно тим же, якби ви завітали на сторінку http://localhost:8000/about в браузері). Запит дає нам об'єкт Crawler, який містить відповідь. Клас Crawler дуже корисний, оскільки він дозволяє перемістити повернутий HTML. Ми використовуємо примірник Crawler для перевірки, що тег H1 у відповіді HTML містить слова About symblog. Ви помітите, що навіть якщо ми розширюємо клас WebTestCase ми досі використовуємо метод Assert, як і раніше, пам'ятайте, клас PageControllerTest раніше є нащадком класу\PHPUnit_Framework_TestCase).

Давайте запустимо PageControllerTest, наступною командою. При написанні тестів її корисно використовувати для виконання тестів тільки для файлу, в якому ви зараз працюєте.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php


Повинно бути виведено повідомлення OK (test 1, 1 assertion) даючи нам знати, що 1 тест (testAbout() був запущений), з 1 затвердженням (assertEquals()).

Спробуйте змінити рядок About symblog на Contact, а потім повторно запустити тест. Тест потерпить невдачу, оскільки Contact не буде знайдений, в результаті чого assertEquals прирівняється до false.
Поверніть рядок About symblog назад перед тим, як ми продовжимо.
Примірник Crawler дозволяє переглядати HTML або XML документи, (що означає, що Crawler буде працювати тільки з відповідями, які повертають HTML або XML). Ми можемо використовувати Crawler для обходу згенерованого відповіді, використовуючи такі методи, як filter(), first(), last() і parents(). Якщо раніше ви використовували jQuery, ви повинні відчувати себе як вдома з класом Crawler. Повний список підтримуваних методів обходу Crawler можна знайти у розділі Testing книги Symfony2.

Домашня сторінка


У той час як тест для сторінки About, був простим, він позначив основні принципи функціонального тестування сторінки сайту.

Створення клієнта

Запит сторінки

Перевірка відповіді


Це простий огляд процесу, насправді існує ще цілий ряд інших кроків, які ми могли б зробити такі як натискання на посилання, заповнення і надсилання форми.
Давайте створимо метод для тестування домашньої сторінки. Ми знаємо, що головна сторінка доступна за маршрутом / і що вона повинна відображати останні записи в блозі. Додайте новий методtestIndex() в клас PageControllerTest, розташований
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testIndex()
{
$client = static::createClient();

$crawler = $client->request('GET', '/');

// Check there are some blog entries on the page
$this->assertTrue($crawler->filter('article.blog')->count() > 0);
}



Ви можете побачити такі ж дії, як і з тестом для сторінки about. Запустіть тест для того, щоб переконатися в тому, що все працює коректно.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php


Давайте тепер підемо трохи далі. Частина функціонального тестування включає в себе здатність повторити те, що користувач буде робити на сайті. Для того, щоб переміщатися по сторінках вашого сайту, користувачі натискають на посилання. Давайте змоделюємо це дія, щоб перевірити правильність посилань на сторінку записи, коли відбувається натискання на заголовок. Оновіть метод testIndex() у класі PageControllerTest// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testIndex()
{
//…

// Find the first link, get the title, ensure this is loaded on the next page
$blogLink = $crawler->filter('article.blog h2 a')->first();
$blogTitle = $blogLink->text();
$crawler = $client->click($blogLink->link());

// Check the h2 has the blog title in it
$this->assertEquals(1, $crawler->filter('h2:contains("'. $blogTitle .'")')->count());
}


Перше, що ми зробимо це використовуємо Crawler, щоб витягти текст з першої посилання назви запису. Це робиться за допомогою фільтра article.blog h2 a. Цей фільтр використовується щоб повернути тег a в теге H2 article.blog статті. Щоб краще це зрозуміти, погляньте на розмітку, використовуваної на домашній сторінці для відображення запису.
<article class="blog">
<div class="date"><time datetime="2016-06-17T14:23:55+03:00">Friday, June 17, 2016</time></div>
<header>
<h2><a href="/1/a-day-with-symfony2">A day with Symfony2</a></h2> 
</header>
//..
</article>


Ви можете побачити структуру фільтра article.blog h2 a розмітці домашньої сторінки. Ви також помітите, що існує кілька в розмітці, це означає що фільтр Crawler повертає колекції. Так як ми хочемо, тільки перше посилання, ми використовуємо first() метод в колекції. Метод click() приймає об'єкт посилання і повертає відповідь у примірнику Crawler. Ви помітите, що об'єкт Crawler є ключовим елементом для функціонального тестування.
Тепер об'єкт Crawler містить відповідь сторінки блогу. Нам потрібно протестувати, що посилання перенаправляє нас на потрібну сторінку. Ми можемо використовувати значення $blogTitle яке ми винесли раніше, щоб перевірити заголовок у відповіді.
Запустіть тести, щоб переконатися, що навігація між домашньою та сторінкою записів працює правильно.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php


Тепер у вас є розуміння того, як переміщатися по сторінках сайту при функціональному тестуванні, давайте перейдемо до тестування форм.

Тестування сторінки контактів


Користувачі symblog можуть відправляти контактні запити, заповнивши форму на сторінці контактівhttp://localhost:8000/contact. Давайте протестуємо, що запити працюють правильно. Спочатку ми повинні визначити, що повинно статися, коли форма успішно відправлена (успішно відправлена в даному випадку означає, без помилок формі).

Перехід на сторінку контактів

Заповнення форми даними

Відправка форми

Перевірка того що лист надіслано

Перевірка повідомлення про успішну відправку


До цього моменту ми дослідили достатньо щоб виконати тільки кроки 1 і 5. Тепер ми розглянемо, як протестувати 3 інших кроку.

Додайте новий метод testContact() в клас PageControllerTest, розташованого
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testContact()
{
$client = static::createClient();

$crawler = $client->request('GET', '/contact');

$this->assertEquals(1, $crawler->filter('h1:contains("Contact symblog")')->count());

// Select based on button value, or id or name for buttons
$form = $crawler->selectButton('Submit')->form();

$form['contact[name]'] = 'name';
$form['contact[email]'] = 'email@email.com';
$form['contact[subject]'] = 'Subject';
$form['contact[body]'] = 'The comment body must be at least 50 characters long as there is a validation constrain on the Enquiry entity';

$crawler = $client->submit($form);

$this->assertEquals(1, $crawler->filter('.blogger-notice:contains("Your contact enquiry was successfully sent. Thank you!")')->count());
}



Ми почнемо з запиту на url /contact і перевіримо, що сторінка містить правильний заголовок H1. Далі ми використовуємо Crawler щоб вибрати кнопку відправки форми. Причиною того, що ми вибираємо кнопку, а не форму є те, що форма може містити декілька кнопок, які ми можемо захотіти натиснути незалежно один від одного. Від обраної кнопки ми можемо отримати форму. Ми можемо встановити значення форми, використовуючи масив []. Нарешті форма передається на клієнтський submit() метод щоб відправити форму. Як зазвичай, тому ми отримуємо примірник Crawler. Використовуючи відповідь Crawler ми перевіряємо, що flash повідомлення присутня в повернутому відповіді. Виконайте тест, щоб перевірити, що все функціонує нормально.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php


Тест не вдався. Ми отримаємо наступне повідомлення PHPUnit

1) Blogger\BlogBundle\Tests\Controller\PageControllerTest::testContact
Failed asserting that 0 matches expected 1.

D:\local\symfony-blog\src\Blogger\BlogBundle\Tests\Controller\PageControllerTest.php:55

FAILURES!
Tests: 3, Assertions: 5, Failures: 1.


Висновок інформує нас про те, що flash повідомлення не може бути знайдено у відповіді після відправки форми. Це сталося тому, що, коли ми перебуваємо в тестовому оточенні, перенаправлення не виконуються. Коли форма успішно перевірена в класі PageController відбувається перенаправлення. Це перенаправлення не відбувається; Нам потрібно явно вказати, що воно має бути дотримано. Причина, по якій перенаправлення не виконуються, проста це відбувається у зв'язку з тим, що ви можете захотіти в першу чергу перевірити поточний відповідь. Ми побачимо пізніше, коли будемо перевіряти вирушило чи повідомлення. Оновіть клас PageControllerTest для установки перенаправлення.

// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testContact()
{
// ..

$crawler = $client->submit($form);

// Need to follow redirect
$crawler = $client->followRedirect();

$this->assertEquals(1, $crawler->filter('.blogger-notice:contains("Your contact enquiry was successfully sent. Thank you!")')->count());
}


Зараз якщо ми запустимо тести PHPUnit ми повинні їх пройти. Давайте тепер подивимося на заключний етап перевірки процесу відправки контактної форми, крок 4, перевірка того що пошта була відправлена. Ми вже знаємо, що пошта не буде доставлена в test оточенні з-за наступної конфігурації.

# app/config/config_test.yml
swiftmailer:
disable_delivery: true


Ми можемо перевірити факт того що листи були відправлені з використанням інформації, зібраної за допомогою веб-профайлера. Це те місце де важливо не перенаправляти клієнта. Перевірка профайлера повинна бути зроблена до того, як відбудеться перенаправлення, інакше інформація у профайлер буде втрачена.
Оновіть метод testContact()
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testContact()
{
// ..

$crawler = $client->submit($form);

// Check email has been sent
if ($profile = $client->getProfile())
{
$swiftMailerProfiler = $profile->getCollector('swiftmailer');

// Only 1 message should have been sent
$this->assertEquals(1, $swiftMailerProfiler->getMessageCount());

// Get the first message
$messages = $swiftMailerProfiler->getMessages();
$message = array_shift($messages);

$symblogEmail = $client->getContainer()->getParameter('blogger_blog.emails.contact_email');
// Check message is being sent to correct address
$this->assertArrayHasKey($symblogEmail, $message->getTo());
}

// Need to follow redirect
$crawler = $client->followRedirect();

$this->assertTrue($crawler->filter('.blogger-notice:contains("Your contact enquiry was successfully sent. Thank you!")')->count() > 0);
}



Після відправки форми ми перевіряємо, чи профайлер, так як це може бути відключене за допомогою конфігурації для поточного оточення.

Підказка

Пам'ятайте, що тести не повинні виконуватися в середовищі, вони повинні бути запущені в робочому оточенні, де речі, такі як профайлер не будуть доступні.


Якщо ми зможемо отримати профайлер ми робимо запит для отримання swiftmailer колектора. swiftmailer колектор працює за кулісами, щоб збирати інформацію про те, як використовується служба електронної пошти. Ми можемо використовувати це, щоб отримати інформацію про те, які листи були відправлені.
Далі ми використовуємо метод getMessageCount(), щоб перевірити, що було відправлено 1 лист. Цього буде достатньо, щоб гарантувати, що пошта була відправлена, але це не перевіряє, що пошта буде відправлена в потрібне місце.
Тепер перезапустим тести і переконаємося, що все працює нормально.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php


Тестування додавання коментарів
Давайте тепер використовуємо знання, отримані з попередніх тестів на сторінці контактної інформації, щоб перевірити процес додавання коментарів до запису. Знову ж ми намітимо, що має статися, коли форма буде успішно відправлено.

Перехід на сторінку записи

Заповнення форми значеннями

Відправка форми

Перевірка того що коментар був доданий в самий кінець всіх коментарів

Також перевірка бічній панелі, блоку Останні коментарі (що коментар перший у списку)


Створіть новий файл, розташований в
src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php
<?php
// src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php

namespace Blogger\BlogBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class BlogControllerTest extends WebTestCase
{
public function testAddBlogComment()
{
$client = static::createClient();

$crawler = $client->request('GET', '/1/a-day-with-symfony');

$this->assertEquals(1, $crawler->filter('h2:contains("A day with Symfony2")')->count());

// Select based on button value, or id or name for buttons
$form = $crawler->selectButton('Submit')->form();

$crawler = $client->submit($form, array(
'blogger_blogbundle_commenttype[user]' => 'name',
'blogger_blogbundle_commenttype[comment]' => 'comment',
));

// Need to follow redirect
$crawler = $client->followRedirect();

// Check comment now is displaying on page, as the last entry. This ensure comments
// are posted in order of oldest to newest
$articleCrawler = $crawler->filter('section .previous-comments article')->last();

$this->assertEquals('name', $articleCrawler->filter('header span.highlight')->text());
$this->assertEquals('comment', $articleCrawler->filter (p')->last()->text());

// Check the sidebar to ensure latest comments are display and there is 10 of them

$this->assertEquals(10, $crawler->filter('aside.sidebar section')->last()
->filter('article')->count()
);

$this->assertEquals('name', $crawler->filter('aside.sidebar section')->last()
->filter('article')->first()
->filter('header span.highlight')->text()
);
}
}



Перш ніж ми почнемо досліджувати код, запустіть тести для цього файлу, щоб переконатися, що все працює правильно.
$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php


PHPUnit повинен повідомити вам, що 1 тест був успішно виконаний. Дивлячись на код для testAddBlogComment() ми можемо бачити створення клієнта, запит сторінки та її перевірку. Потім переходимо до отримання форми Додавання коментаря та відправки форми. Спосіб, яким ми заповнили значення форми трохи відрізняється від попередньої версії. На цей раз ми використовуємо 2-й аргумент клієнта submit() метод додавання значень форми.

Підказка

Ми могли б також використовувати об'єктно-орієнтований інтерфейс для установки значень полів форми. Приклади показані нижче.

// Tick a checkbox
$form['show_emal']->tick();

// Select an option or a radio
$form['gender']->select('Male');



Після відправки форми, ми запитуємо клієнт який повинен стежити за перенаправленням, щоб ми могли перевірити відповідь. Ми знову використовуємо Crawler, щоб отримати останній коментар в блозі, який повинен бути тим який ми тільки що відправили. Нарешті ми перевіряємо останні коментарі в бічній панелі, щоб перевірити, що коментар є першим у списку.

Репозиторій Blog
В останній частині функціонального тестування ми досліджуємо тестування Doctrine 2 Репозиторію. Створіть новий файл src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php
<?php
// src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php

namespace Blogger\BlogBundle\Tests\Repository;

use Blogger\BlogBundle\Entity\Repository\BlogRepository; 
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class BlogRepositoryTest extends WebTestCase
{
/**
* @var \Blogger\BlogBundle\Entity\Repository\BlogRepository
*/
private $blogRepository;

public function setUp()
{
$kernel = static::createKernel();
$kernel->boot();
$this->blogRepository = $kernel->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('BloggerBlogBundle:Blog');
}

public function testGetTags()
{
$tags = $this->blogRepository->getTags();

$this->assertTrue(count($tags) > 1);
$this->assertContains('symblog', $tags);
}

public function testGetTagWeights()
{
$tagsWeight = $this->blogRepository->getTagWeights(
array('php', 'code', 'code', 'symblog', 'blog')
);

$this->assertTrue(count($tagsWeight) > 1);

// Test case where count is over max weight of 5
$tagsWeight = $this->blogRepository->getTagWeights(
array_fill(0, 10, 'php')
);

$this->assertTrue(count($tagsWeight) >= 1);

// Test case with multiple counts over max weight of 5
$tagsWeight = $this->blogRepository->getTagWeights(
array_merge(array_fill(0, 10, 'php'), array_fill(0, 2, 'html'), array_fill(0, 6, 'js'))
);

$this->assertEquals(5, $tagsWeight['php']);
$this->assertEquals(3, $tagsWeight['js']);
$this->assertEquals(1, $tagsWeight['html']);

// Test empty case
$tagsWeight = $this->blogRepository->getTagWeights(array());

$this->assertEmpty($tagsWeight);
}
}


Оскільки ми хочемо виконати тести, які потребують правильного підключення до бази даних ми знову розширимо WebTestCase, так як це дозволяє завантажувати Symfony2 Kernel. Запустіть тест для цього файлу з допомогою наступної команди.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php


Покриття коду
Покриття коду дає нам розуміння того, які частини коду виконуються, коли запускаються тести. За допомогою цього ми можемо побачити частини нашого коду, які не покриті тестами і визначити, чи треба писати тест для них.

Для виведення покриття коду для вашого додатки введіть в консолі

$ phpunit --coverage-html ./phpunit-report -c app/


Врахуйте, щоб команда спрацювала, необхідний активоване xdebug в php.
Це виведе аналіз покриття коду в папку phpunit-report в корені проекту. Відкрийте файл index.html з цієї папки у вашому браузері, щоб побачити результат аналізу.



Висновок



Ми розглянули ряд ключових областей у тестуванні. Досліджували як модульне так і функціональне тестування, щоб переконатися в тому, що наш сайт функціонує нормально. Ми побачили, як симулювати запити браузера і як використовувати клас Symfony2 Crawler для перевірки відповіді цих запитів.

Джерела і допоміжні матеріали:

https://symfony.com/
http://tutorial.symblog.co.uk/
https://phpunit.de/

Post Scriptum
дякую Всім за увагу і зауваження зроблені за проектом, якщо у вас виникли труднощі чи питання, отписывайтесь в коментарі або особисті повідомлення, додавайтеся в друзі.

Частина 1 — Конфігурація Symfony2 і шаблонів
Частина 2 — Сторінка з контактною інформацією: валідатори, форми та електронна пошта
Частина 3 — Doctrine 2 і Фікстури даних
Частина 4 — Модель коментарів, Репозиторій та Міграції Doctrine 2
Частина 5 — Twig розширення, Бічна панель(sidebar) і Assetic


Також, якщо Вам сподобалося керівництво ви можете поставити зірку репозиторія проекту або підписатися. Спасибі.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.