Запуск тестов PHPUnit в определенном порядке

Есть ли способ получить тесты внутри TestCase для запуска в определенном порядке? Например, я хочу отделить жизненный цикл объекта от создания, чтобы использовать его для уничтожения, но мне нужно убедиться, что объект настроен первым, прежде чем запускать другие тесты.

Возможно, в ваших тестах есть проблема с дизайном.

Обычно каждый тест не должен зависеть от каких-либо других тестов, поэтому они могут работать в любом порядке.

Каждый тест должен создавать и уничтожать все, что нужно для запуска, что было бы идеальным подходом, вы никогда не должны делиться объектами и состояниями между тестами.

Можете ли вы уточнить, почему вам нужен один и тот же объект для N тестов?

PHPUnit поддерживает тестовые зависимости через аннотацию @depends .

Ниже приведен пример из документации, в которой тесты будут выполняться в порядке, удовлетворяющем зависимостям, причем каждый зависимый тест передает аргумент следующему:

 class StackTest extends PHPUnit_Framework_TestCase { public function testEmpty() { $stack = array(); $this->assertEmpty($stack); return $stack; } /** * @depends testEmpty */ public function testPush(array $stack) { array_push($stack, 'foo'); $this->assertEquals('foo', $stack[count($stack)-1]); $this->assertNotEmpty($stack); return $stack; } /** * @depends testPush */ public function testPop(array $stack) { $this->assertEquals('foo', array_pop($stack)); $this->assertEmpty($stack); } } 

Однако важно отметить, что тесты с неразрешенными зависимостями не будут выполнены (желательно, так как это быстро обращает внимание на неудачный тест). Поэтому важно уделять пристальное внимание при использовании зависимостей.

Если вы хотите, чтобы ваши тесты делились различными вспомогательными объектами и настройками, вы можете использовать setUp() , tearDown() для добавления в свойство sharedFixture .

PHPUnit позволяет использовать аннотацию @depends, которая задает зависимые тестовые примеры и позволяет передавать аргументы между зависимыми тестовыми примерами.

На мой взгляд, сделайте следующий сценарий, когда мне нужно протестировать создание и уничтожение определенного ресурса.

Первоначально у меня было два метода: a. testCreateResource и b. testDestroyResource

а. testCreateResource

 <?php $app->createResource('resource'); $this->assertTrue($app->hasResource('resource')); ?> 

б. testDestroyResource

 <?php $app->destroyResource('resource'); $this->assertFalse($app->hasResource('resource')); ?> 

Я думаю, что это плохая идея, так как testDestroyResource зависит от testCreateResource. И лучше будет делать

а. testCreateResource

 <?php $app->createResource('resource'); $this->assertTrue($app->hasResource('resource')); $app->deleteResource('resource'); ?> 

б. testDestroyResource

 <?php $app->createResource('resource'); $app->destroyResource('resource'); $this->assertFalse($app->hasResource('resource')); ?> 

Правильный ответ для этого – правильный файл конфигурации для тестов. У меня была та же проблема и исправлена ​​ее, создав testuite с необходимыми тестовыми файлами:

 phpunit.xml: <phpunit colors="true" bootstrap="./tests/bootstrap.php" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" strict="true" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" stopOnRisky="false" > <testsuites> <testsuite name="Your tests"> <file>file1</file> //this will be run before file2 <file>file2</file> //this depends on file1 </testsuite> </testsuites> </phpunit> во phpunit.xml: <phpunit colors="true" bootstrap="./tests/bootstrap.php" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" strict="true" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" stopOnRisky="false" > <testsuites> <testsuite name="Your tests"> <file>file1</file> //this will be run before file2 <file>file2</file> //this depends on file1 </testsuite> </testsuites> </phpunit> во phpunit.xml: <phpunit colors="true" bootstrap="./tests/bootstrap.php" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" strict="true" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" stopOnRisky="false" > <testsuites> <testsuite name="Your tests"> <file>file1</file> //this will be run before file2 <file>file2</file> //this depends on file1 </testsuite> </testsuites> </phpunit> 

Там действительно проблема с вашими испытаниями, если им нужно работать в определенном порядке. Каждый тест должен быть полностью независим от других: он помогает вам с локализацией дефектов и позволяет получать повторяющиеся (и, следовательно, отлаживаемые) результаты.

Оформить заказ на этот сайт для получения полной информации о идеях / информации, о том, как оценивать ваши тесты таким образом, чтобы избежать таких проблем.

Альтернативное решение: используйте статические (!) Функции в своих тестах для создания элементов многократного использования. Например (я использую Selenium IDE для записи тестов и phpunit-selenium (github) для запуска теста внутри браузера)

 class LoginTest extends SeleniumClearTestCase { public function testAdminLogin() { self::adminLogin($this); } public function testLogout() { self::adminLogin($this); self::logout($this); } public static function adminLogin($t) { self::login($t, 'john.smith@gmail.com', 'pAs$w0rd'); $t->assertEquals('John Smith', $t->getText('css=span.hidden-xs')); } // @source LoginTest.se public static function login($t, $login, $pass) { $t->open('/'); $t->click("xpath=(//a[contains(text(),'Log In')])[2]"); $t->waitForPageToLoad('30000'); $t->type('name=email', $login); $t->type('name=password', $pass); $t->click("//button[@type='submit']"); $t->waitForPageToLoad('30000'); } // @source LogoutTest.se public static function logout($t) { $t->click('css=span.hidden-xs'); $t->click('link=Logout'); $t->waitForPageToLoad('30000'); $t->assertEquals('PANEL', $t->getText("xpath=(//a[contains(text(),'Panel')])[2]")); } } из class LoginTest extends SeleniumClearTestCase { public function testAdminLogin() { self::adminLogin($this); } public function testLogout() { self::adminLogin($this); self::logout($this); } public static function adminLogin($t) { self::login($t, 'john.smith@gmail.com', 'pAs$w0rd'); $t->assertEquals('John Smith', $t->getText('css=span.hidden-xs')); } // @source LoginTest.se public static function login($t, $login, $pass) { $t->open('/'); $t->click("xpath=(//a[contains(text(),'Log In')])[2]"); $t->waitForPageToLoad('30000'); $t->type('name=email', $login); $t->type('name=password', $pass); $t->click("//button[@type='submit']"); $t->waitForPageToLoad('30000'); } // @source LogoutTest.se public static function logout($t) { $t->click('css=span.hidden-xs'); $t->click('link=Logout'); $t->waitForPageToLoad('30000'); $t->assertEquals('PANEL', $t->getText("xpath=(//a[contains(text(),'Panel')])[2]")); } } 

Хорошо, и теперь я могу использовать эти повторно используемые элементы в другом тесте 🙂 Например:

 class ChangeBlogTitleTest extends SeleniumClearTestCase { public function testAddBlogTitle() { self::addBlogTitle($this,'I like my boobies'); self::cleanAddBlogTitle(); } public static function addBlogTitle($t,$title) { LoginTest::adminLogin($t); $t->click('link=ChangeTitle'); ... $t->type('name=blog-title', $title); LoginTest::logout($t); LoginTest::login($t, 'paris@gmail.com','hilton'); $t->screenshot(); // take some photos :) $t->assertEquals($title, $t->getText('...')); } public static function cleanAddBlogTitle() { $lastTitle = BlogTitlesHistory::orderBy('id')->first(); $lastTitle->delete(); } из class ChangeBlogTitleTest extends SeleniumClearTestCase { public function testAddBlogTitle() { self::addBlogTitle($this,'I like my boobies'); self::cleanAddBlogTitle(); } public static function addBlogTitle($t,$title) { LoginTest::adminLogin($t); $t->click('link=ChangeTitle'); ... $t->type('name=blog-title', $title); LoginTest::logout($t); LoginTest::login($t, 'paris@gmail.com','hilton'); $t->screenshot(); // take some photos :) $t->assertEquals($title, $t->getText('...')); } public static function cleanAddBlogTitle() { $lastTitle = BlogTitlesHistory::orderBy('id')->first(); $lastTitle->delete(); } 
  • Таким образом, вы можете построить иерархию тестов.
  • Вы можете сохранить свойство, чтобы каждый тестовый пример был полностью отделен от другого (если вы очищаете БД после каждого теста).
  • И самое главное, если, например, способ входа в систему изменится в будущем, вы измените только класс LoginTest, и вам не потребуется правильная часть входа в другие тесты (они должны работать после обновления LoginTest) 🙂

Когда я запускаю тест, мой скрипт очищает db ad начало. Выше я использую свой класс SeleniumClearTestCase (я делаю скриншот () и другие приятные функции там), это расширение MigrationToSelenium2 (от github, до порта, записанного в Firefox, с использованием seleniumIDE + ff-плагина «Selenium IDE: PHP Formatters»), который является расширением мой класс LaravelTestCase (это копия Illuminate \ Foundation \ Testing \ TestCase, но не расширяет PHPUnit_Framework_TestCase), который настраивает laravel для доступа к красноречивому, когда мы хотим очистить БД в конце теста), который является расширением PHPUnit_Extensions_Selenium2TestCase. Чтобы настроить laravel eloquent, у меня также есть функция SeleniumClearTestCase createApplication (которая вызывается в setUp , и я беру эту функцию из laral test / TestCase)