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

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

13.08.2008 19:02:23
Вы можете добавить @depends, как описано в ответе ниже, и использование setup () и teardown () также является хорошей идеей, но тесты просто запускаются сверху вниз ...
Andrew 10.06.2015 15:30:28
Еще один вариант использования, который, кажется, не был рассмотрен: возможно, все тесты являются атомарными, но некоторые тесты являются МЕДЛЕННЫМИ. Я хочу, чтобы быстрые тесты запускались как можно скорее, чтобы они могли быстро проваливаться, а любые медленные тесты должны запускаться без изменений в последнюю очередь после того, как я уже видел другие проблемы и могу сразу их найти.
Kzqai 19.03.2016 21:31:41
8 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

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

50
13.08.2008 20:13:42
Это не кажется мне правильным. Цель модульного теста - проверить весь блок. Смысл иметь единицу в том, чтобы группировать вещи, которые должны зависеть друг от друга. Написание тестов, которые тестируют отдельные методы без контекста для класса, сродни пропаганде процедурного программирования поверх oo, потому что вы защищаете то, что отдельные функции не должны зависеть от одних и тех же данных.
doliver 4.05.2013 20:35:42
Я не согласен с вашей точкой зрения. Выходные данные теста создания экземпляра являются допустимым объектом, который может использоваться другими тестами в вашем наборе тестов. Нет необходимости создавать новый объект для каждого теста, особенно если конструктор сложен.
pedromanoel 12.07.2013 15:08:16
Если конструктор сложен, вы делаете что-то не так, возможно, ваш класс делает слишком много. Пожалуйста, прочитайте о "SOLID", более конкретно о "Single Responsibility Pattern (SRP)", также вы должны "подделать" зависимости в ваших тестах, используя mocks, пожалуйста, прочитайте также о "mocks, fakes and stubs".
Fabio Gomes 13.07.2013 22:15:36
Там также может быть практическая причина. Например, если очистка, которую вам нужно сделать, отнимает много времени, вы можете использовать функцию tearDownAfterClass, чтобы запустить ее только один раз. Если один конкретный тест требует чистого листа, то вы должны либо убедиться, что тест запускается первым, либо вручную вызвать функцию tearDownAfterClass при его запуске, в результате чего он будет выполнен дважды. Да, это, вероятно, признак того, что с классом тестов что-то не так, но есть законные случаи, когда упорядочивание тестов полезно.
Benubird 23.07.2014 13:54:18
По крайней мере, для тестирования базы данных часто требуется повторное использование объектов (по крайней мере, соединение). PHPUnit также ссылается на это: phpunit.de/manual/current/en/database.html (см .: Совет: используйте свой собственный абстрактный
emfi 28.07.2017 08:12:55

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

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

1
4.01.2013 01:50:04
PHPUnit поддерживает тестовые зависимости через @depends.
mjs 16.12.2009 14:47:55

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

8
23.08.2008 22:30:48
Можешь еще assertEquals()и тд в setUp()? Это плохая практика?
jchook 17.12.2016 22:47:26

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);
    }
}

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

140
17.12.2014 22:50:08
Для PHPUnit это означает, что тестовая функция будет пропущена, если предыдущий тест не был выполнен. Это не создает тестовый заказ.
Dereckson 9.02.2014 19:12:11
Просто чтобы расширить @Dereckson, @dependsаннотация приведет к пропуску теста, если тест, который зависит от того, либо еще не был выполнен, либо завершился неудачно, когда он выполнялся.
km6zla 21.08.2014 00:39:17
@ km6zla означает ли это, что когда мы помещаем или записываем testPop()метод testPush()в файл раньше, testPop()он никогда не будет выполнен и всегда будет пропущен?
Top-Master 28.03.2019 06:34:38

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

7
29.12.2009 10:22:31

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

Первоначально у меня было два метода, а. 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'));
?>
2
28.03.2010 10:42:21
-1 Во втором подходе destroyResource также зависит от createResource, но это явно не установлено. Если createResource завершится неудачно, UTesting Framework ошибочно укажет, что destroyResource не работает
Tivie 26.11.2013 22:33:44

Альтернативное решение: используйте статические (!) Функции в своих тестах для создания повторно используемых элементов. Например (я использую 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 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) :)

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

2
17.06.2016 21:12:40
Вот больше подробностей для запуска теста, записанного в Selenium IDE на Laravel 5.2 и phpUnit: stackoverflow.com/questions/33845828/…
Kamil Kiełczewski 18.06.2016 21:05:35

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

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>
18
26.07.2017 10:09:27
я думаю, что это единственное надежное решение
emfi 28.07.2017 08:15:39
Отлично! Не каждый тест является юнит-тестом; например, при написании тестов HTTP Request или Feature изменения состояния могут потребоваться для всех классов тестов, и в таких случаях это наиболее надежный подход к выполнению тестов в значимой последовательности.
Ben Johnson 13.08.2018 15:49:50
Кто-нибудь еще тестировал, верно ли это для параллельного выполнения тестов PHPUnit?
Smamatti 24.11.2019 12:54:39