Есть ли способ заставить тесты внутри TestCase
запускаться в определенном порядке? Например, я хочу разделить жизненный цикл объекта от создания и использования до уничтожения, но мне нужно убедиться, что объект настроен, прежде чем запускать другие тесты.
Запускать тесты PHPUnit в определенном порядке
Ответы (8)
Возможно, в ваших тестах есть проблема с дизайном.
Обычно каждый тест не должен зависеть ни от каких других тестов, поэтому их можно запускать в любом порядке.
Каждый тест должен создавать и уничтожать все, что ему нужно для запуска, это был бы идеальный подход, вы никогда не должны делиться объектами и состояниями между тестами.
Можете ли вы уточнить, почему вам нужен один и тот же объект для N тестов?
setUp()
для подготовки макетов, если есть что-то, что вы собираетесь повторять для каждого теста.
- person Xavi Montero; 31.03.2018
EmailAdapter->sendEmail()
и проверяем, как это изменило состояние. [продолжает]
- person Xavi Montero; 16.02.2019
$adapter->save( $object );
, а затем $actualObject = $adapter->loadById( $id );
— это проверка комбинации двух методов, и это функциональное тестирование, а не модульное тестирование. Если мы хотим конкретно придерживаться модуля, его следует разделить на 2 теста: по одному для каждого модуля: тест 1) с настройкой, очищающей БД, сохраняя и подтверждая, что БД содержит данные, тест 2) установка устанавливает фикстуру, загрузку и подтвердите, что загруженный объект соответствует прибору.
- person Xavi Montero; 16.02.2019
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);
}
}
Однако важно отметить, что тесты с неразрешенными зависимостями не будут выполняться (желательно, так как это быстро привлекает внимание к неудачному тесту). Поэтому важно уделять пристальное внимание при использовании зависимостей.
@depends
приведет к тому, что тест будет пропущен, если тест, который зависит от либо, еще не был запущен или не прошел, когда он выполнялся.
- person km6zla; 21.08.2014
testPop()
перед testPush()
в файле, тогда testPop()
никогда не будет выполняться и всегда будет пропущен?
- person Top-Master; 28.03.2019
Правильный ответ на этот вопрос — правильный конфигурационный файл для тестов. У меня была та же проблема, и я исправил ее, создав набор тестов с необходимым порядком тестовых файлов:
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>
Если вы хотите, чтобы ваши тесты совместно использовали различные вспомогательные объекты и настройки, вы можете использовать setUp()
, tearDown()
для добавления к свойству sharedFixture
.
assertEquals()
и т. д. в setUp()
? Это плохая практика?
- person jchook; 18.12.2016
PHPUnit позволяет использовать аннотацию @depends, которая определяет зависимые тестовые случаи и позволяет передавать аргументы между зависимыми тестовыми примерами.
Альтернативное решение: используйте статические (!) функции в своих тестах для создания повторно используемых элементов. Например (я использую 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, '[email protected]', '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, '[email protected]','hilton');
$t->screenshot(); // take some photos :)
$t->assertEquals($title, $t->getText('...'));
}
public static function cleanAddBlogTitle() {
$lastTitle = BlogTitlesHistory::orderBy('id')->first();
$lastTitle->delete();
}
- Таким образом, вы можете построить иерархию ваших тестов.
- Вы можете сохранить свойство, что каждый тестовый пример полностью отделен от другого (если вы очищаете БД после каждого теста).
- И самое главное, если, например, способ входа в систему изменится в будущем, вы только модифицируете класс LoginTest, и вам не нужна правильная часть входа в другие тесты (они должны работать после обновления LoginTest) :)
Когда я запускаю тест, мой сценарий очищает базу данных в начале. Выше я использую свой класс 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)
На мой взгляд, возьмите следующий сценарий, в котором мне нужно протестировать создание и уничтожение определенного ресурса.
Изначально у меня было два метода: а. testCreateResource и б. 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'));
?>
С вашими тестами действительно есть проблема, если их нужно запускать в определенном порядке. Каждый тест должен быть полностью независимым от других: он помогает локализовать дефект и позволяет получить воспроизводимые (и, следовательно, отлаживаемые) результаты.
Посетите этот сайт, чтобы получить массу идей/информации о том, как провести тесты таким образом, чтобы избежать такие вопросы.