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

50

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

Вы можете добавить @depends, как описано в ответе ниже, и использование setup () и teardown () также является хорошей идеей, но тесты просто запускаются сверху вниз ... Andrew
Один дополнительный вариант использования, который, кажется, не был охвачен: Возможно, все тесты являются атомарными, но некоторые тесты являются МЕДЛЕННЫМИ. Я хочу, чтобы быстрые тесты выполнялись как можно скорее, чтобы они могли быстро завершаться неудачей, и любые медленные тесты должны запускаться мертвыми в последнюю очередь после того, как я уже видел другие проблемы и мог сразу их найти. Kzqai

Ваш Ответ

8   ответов
50

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

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

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

Это не кажется мне правильным. Цель модульного теста - проверить весь блок. Смысл иметь единицу состоит в том, чтобы группировать вещи, которые должны зависеть друг от друга. Написание тестов, которые тестируют отдельные методы без контекста для класса, сродни пропаганде процедурного программирования поверх oo, поскольку вы защищаете то, что отдельные функции не должны зависеть от одних и тех же данных.
Если конструктор сложен, вы делаете что-то не так, возможно, ваш класс делает слишком много. Пожалуйста, прочитайте о "SOLID", более конкретно о "Шаблоне одиночной ответственности (SRP)", также вы должны "подделать" зависимости в ваших тестах с использованием mocks, пожалуйста, прочитайте о "mocks, fakes and stubs" & quot; тоже.
Я не согласен с вашей точкой зрения. Результатом теста создания экземпляра является действительный объект, который может использоваться другими тестами в вашем наборе тестов. Нет необходимости создавать новый объект для каждого теста, особенно если конструктор является сложным.
По крайней мере, для тестирования базы данных часто требуется повторное использование объектов (по крайней мере, соединение). PHPUnit также ссылается на это:phpunit.de/manual/current/en/database.html (см .: Совет: используйте свой собственный абстрактный тестовый набор баз данных)
Там также может быть практическая причина. Например, если очистка, которую вам нужно сделать, отнимает много времени, вы можете использовать функцию tearDownAfterClass, чтобы запустить ее только один раз. Если один конкретный тест требует чистого листа, то вы должны либо убедиться, что тест запускается первым, либо вручную вызвать функцию tearDownAfterClass при его запуске, в результате чего он будет выполнен дважды. Да, это, вероятно, признак того, что с классом тестов что-то не так, но есть законные случаи, когда упорядочивание тестов полезно.
8

еня была та же проблема, и я исправил ее, создав набор тестов с необходимым порядком файлов тестов:

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>
я думаю, что это единственное надежное решение
Отлично! Не каждый тест является юнит-тестом; например, при написании тестов HTTP Request или Feature изменения состояния могут потребоваться для всех классов тестов, и в таких случаях это наиболее надежный подход к выполнению тестов в значимой последовательности.
130

@зависит аннотаций.

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

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

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

Не решает проблему с тестовым заказом
Для PHPUnit это означает, что тестовая функция будет пропущена, если предыдущий тест не был выполнен. Это не создает тестовый заказ.
Просто чтобы расширить на @Dereckson,@depends аннотация приведет к пропуску теста, если тест зависит отeither еще не запущен или не прошел, когда он запускался.
8

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

Ты еще можешьassertEquals()и т. д. вsetUp()? Это плохая практика?
7

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

2

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

Первоначально у меня было два метода, а. 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'));
?>
-1 Во втором подходе destroyResource также зависит от createResource, но он явно не установлен так. Если createResource завершится неудачно, UTesting Framework ошибочно укажет, что destroyResource не работает
1

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

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

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

Используйте статические (!) Функции в своих тестах для создания повторно используемых элементов. Например (я использую 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();
    }
In this way, you can build hierarchy of you tests. You can steel keep property that each test case is totaly separate from other (if you clean DB after each test). And most important, if for instance, the way of login change in future, you only modify LoginTest class, and you don'n need correct login part in other tests (they should work after update LoginTest) :)

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

Вот больше подробностей для запуска теста, записанного в Selenium IDE на Laravel 5.2 и phpUnit:stackoverflow.com/questions/33845828/…

Похожие вопросы