按一定的顺序运行PHPUnit测试

时间:2022-02-23 07:07:20

Is there a way to get the tests inside of a TestCase to run in a certain order? For example, I want to separate the life cycle of an object from creation to use to destruction but I need to make sure that the object is set up first before I run the other tests.

有没有办法让测试用例中的测试按一定的顺序运行?例如,我希望将对象的生命周期从创建到使用到销毁分开,但我需要确保在运行其他测试之前先设置对象。

8 个解决方案

#1


45  

Maybe there is a design problem in your tests.

也许在你的测试中有一个设计问题。

Usually each test must not depend on any other tests, so they can run in any order.

通常每个测试都不能依赖于任何其他测试,因此它们可以以任何顺序运行。

Each test needs to instantiate and destroy everything it needs to run, that would be the perfect approach, you should never share objects and states between tests.

每个测试都需要实例化并销毁它需要运行的所有东西,这是最好的方法,您不应该在测试之间共享对象和状态。

Can you be more specific about why you need the same object for N tests?

关于为什么N个测试需要相同的对象,您能更具体一点吗?

#2


118  

PHPUnit supports test dependencies via the @depends annotation.

PHPUnit通过@depends注释支持测试依赖项。

Here is an example from the documentation where tests will be run in an order that satisfies dependencies, with each dependent test passing an argument to the next:

这里有一个来自文档的示例,其中测试将按照满足依赖关系的顺序运行,每个依赖测试将一个参数传递给下一个:

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

However, it's important to note that tests with unresolved dependencies will not be executed (desirable, as this brings attention quickly to the failing test). So, it's important to pay close attention when using dependencies.

但是,需要注意的是,带有未解析依赖项的测试将不会被执行(需要这样做,因为这会很快地将注意力转移到失败的测试上)。因此,在使用依赖项时,密切关注是很重要的。

#3


8  

If you want your tests to share various helper objects and settings, you can use setUp(), tearDown() to add to the sharedFixture property.

如果希望测试共享各种帮助器对象和设置,可以使用setUp()、tearDown()添加到sharedFixture属性。

#4


7  

PHPUnit allows the use of '@depends' annotation which specifies dependent test cases and allows passing arguments between dependent test cases.

PHPUnit允许使用“@depends”注释,该注释指定依赖测试用例,并允许在依赖测试用例之间传递参数。

#5


6  

The correct answer for this is a proper configuration file for tests. I had the same problem and fixed it by creating testsuite with necessary test files order:

正确的答案是测试的正确配置文件。我也遇到了同样的问题,我通过创建testsuite来解决这个问题,并建立了必要的测试文件顺序:

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>

#6


2  

In my view, take the following scenario where I need to test creation and destroying of a particular resource.

在我看来,采用以下场景,我需要测试创建和销毁特定资源。

Initially I had two methods, a. testCreateResource and b. testDestroyResource

最初我有两种方法,a. testCreateResource和b. testDestroyResource。

a. testCreateResource

答:testCreateResource

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

b. testDestroyResource

b . testDestroyResource

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

I think this is a bad idea, as testDestroyResource depends upon testCreateResource. And a better practice would be to do

我认为这是个坏主意,因为test驱逐舰资源依赖于testCreateResource。更好的做法是

a. testCreateResource

答:testCreateResource

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

b. testDestroyResource

b . testDestroyResource

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

#7


1  

There really is a problem with your tests if they need to run in a certain order. Each test should be totally independent of the others: it helps you with defect localization, and allows you to get repeatable (and therefore debuggable) results.

如果您的测试需要按照一定的顺序运行,那么您的测试确实存在问题。每个测试应该完全独立于其他测试:它帮助您进行缺陷本地化,并允许您获得可重复的(因此可调试的)结果。

Checkout this site for a whole load of ideas / information, about how to factor your tests in a manner where you avoid these kinds of issues.

在这个站点上查看大量的想法/信息,关于如何以一种避免这些问题的方式来影响测试。

#8


1  

Alternative solution: Use static(!) functions in your tests to create reusable elements. For instance (I use selenium IDE to record tests and phpunit-selenium (github) to run test inside browser)

替代解决方案:在测试中使用静态(!)函数来创建可重用的元素。例如(我使用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]"));
    }
}

Ok, and now, i can use this reusable elements in other test :) For instance:

现在,我可以在其他测试中使用这个可重用的元素。

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();
    }
  • 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).
  • 您可以保持每个测试用例是完全独立于其他测试用例的属性(如果您在每次测试之后清洗DB)。
  • 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) :)
  • 最重要的是,如果以后登录方式发生变化,您只需要修改LoginTest类,其他测试中不需要正确的登录部分(更新LoginTest后才能工作):

When I run test my script clean up db ad the begining. Above I use my SeleniumClearTestCase class (I make screenshot() and other nice functions there) it is extension of MigrationToSelenium2 (from github, to port recorded tests in firefox using seleniumIDE + ff plugin "Selenium IDE: PHP Formatters" ) which is extension of my class LaravelTestCase (it is copy of Illuminate\Foundation\Testing\TestCase but not extends PHPUnit_Framework_TestCase) which setup laravel to have access to eloquent when we want to clean DB at the end of test) which is extension of PHPUnit_Extensions_Selenium2TestCase. To set up laravel eloquent I have also in SeleniumClearTestCase function createApplication (which is called at setUp, and I take this function from laral test/TestCase)

当我运行测试时,我的脚本从一开始就会清理db。上面我使用了SeleniumClearTestCase类(我做了截屏()和其他很好的函数)它是MigrationToSelenium2的扩展(从github,用seleniumIDE + ff插件“Selenium IDE:它是我的类LaravelTestCase(它是illumin\ Foundation Testing\TestCase的副本,但不扩展PHPUnit_Framework_TestCase)的扩展,当我们希望在测试结束时清理DB时,laravel可以使用这个扩展),这是PHPUnit_Extensions_Selenium2TestCase的扩展。为了更好地设置laravel函数,我还在SeleniumClearTestCase函数createApplication(在安装时调用,我从laral test/TestCase取这个函数)中安装了这个函数

#1


45  

Maybe there is a design problem in your tests.

也许在你的测试中有一个设计问题。

Usually each test must not depend on any other tests, so they can run in any order.

通常每个测试都不能依赖于任何其他测试,因此它们可以以任何顺序运行。

Each test needs to instantiate and destroy everything it needs to run, that would be the perfect approach, you should never share objects and states between tests.

每个测试都需要实例化并销毁它需要运行的所有东西,这是最好的方法,您不应该在测试之间共享对象和状态。

Can you be more specific about why you need the same object for N tests?

关于为什么N个测试需要相同的对象,您能更具体一点吗?

#2


118  

PHPUnit supports test dependencies via the @depends annotation.

PHPUnit通过@depends注释支持测试依赖项。

Here is an example from the documentation where tests will be run in an order that satisfies dependencies, with each dependent test passing an argument to the next:

这里有一个来自文档的示例,其中测试将按照满足依赖关系的顺序运行,每个依赖测试将一个参数传递给下一个:

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

However, it's important to note that tests with unresolved dependencies will not be executed (desirable, as this brings attention quickly to the failing test). So, it's important to pay close attention when using dependencies.

但是,需要注意的是,带有未解析依赖项的测试将不会被执行(需要这样做,因为这会很快地将注意力转移到失败的测试上)。因此,在使用依赖项时,密切关注是很重要的。

#3


8  

If you want your tests to share various helper objects and settings, you can use setUp(), tearDown() to add to the sharedFixture property.

如果希望测试共享各种帮助器对象和设置,可以使用setUp()、tearDown()添加到sharedFixture属性。

#4


7  

PHPUnit allows the use of '@depends' annotation which specifies dependent test cases and allows passing arguments between dependent test cases.

PHPUnit允许使用“@depends”注释,该注释指定依赖测试用例,并允许在依赖测试用例之间传递参数。

#5


6  

The correct answer for this is a proper configuration file for tests. I had the same problem and fixed it by creating testsuite with necessary test files order:

正确的答案是测试的正确配置文件。我也遇到了同样的问题,我通过创建testsuite来解决这个问题,并建立了必要的测试文件顺序:

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>

#6


2  

In my view, take the following scenario where I need to test creation and destroying of a particular resource.

在我看来,采用以下场景,我需要测试创建和销毁特定资源。

Initially I had two methods, a. testCreateResource and b. testDestroyResource

最初我有两种方法,a. testCreateResource和b. testDestroyResource。

a. testCreateResource

答:testCreateResource

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

b. testDestroyResource

b . testDestroyResource

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

I think this is a bad idea, as testDestroyResource depends upon testCreateResource. And a better practice would be to do

我认为这是个坏主意,因为test驱逐舰资源依赖于testCreateResource。更好的做法是

a. testCreateResource

答:testCreateResource

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

b. testDestroyResource

b . testDestroyResource

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

#7


1  

There really is a problem with your tests if they need to run in a certain order. Each test should be totally independent of the others: it helps you with defect localization, and allows you to get repeatable (and therefore debuggable) results.

如果您的测试需要按照一定的顺序运行,那么您的测试确实存在问题。每个测试应该完全独立于其他测试:它帮助您进行缺陷本地化,并允许您获得可重复的(因此可调试的)结果。

Checkout this site for a whole load of ideas / information, about how to factor your tests in a manner where you avoid these kinds of issues.

在这个站点上查看大量的想法/信息,关于如何以一种避免这些问题的方式来影响测试。

#8


1  

Alternative solution: Use static(!) functions in your tests to create reusable elements. For instance (I use selenium IDE to record tests and phpunit-selenium (github) to run test inside browser)

替代解决方案:在测试中使用静态(!)函数来创建可重用的元素。例如(我使用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]"));
    }
}

Ok, and now, i can use this reusable elements in other test :) For instance:

现在,我可以在其他测试中使用这个可重用的元素。

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();
    }
  • 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).
  • 您可以保持每个测试用例是完全独立于其他测试用例的属性(如果您在每次测试之后清洗DB)。
  • 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) :)
  • 最重要的是,如果以后登录方式发生变化,您只需要修改LoginTest类,其他测试中不需要正确的登录部分(更新LoginTest后才能工作):

When I run test my script clean up db ad the begining. Above I use my SeleniumClearTestCase class (I make screenshot() and other nice functions there) it is extension of MigrationToSelenium2 (from github, to port recorded tests in firefox using seleniumIDE + ff plugin "Selenium IDE: PHP Formatters" ) which is extension of my class LaravelTestCase (it is copy of Illuminate\Foundation\Testing\TestCase but not extends PHPUnit_Framework_TestCase) which setup laravel to have access to eloquent when we want to clean DB at the end of test) which is extension of PHPUnit_Extensions_Selenium2TestCase. To set up laravel eloquent I have also in SeleniumClearTestCase function createApplication (which is called at setUp, and I take this function from laral test/TestCase)

当我运行测试时,我的脚本从一开始就会清理db。上面我使用了SeleniumClearTestCase类(我做了截屏()和其他很好的函数)它是MigrationToSelenium2的扩展(从github,用seleniumIDE + ff插件“Selenium IDE:它是我的类LaravelTestCase(它是illumin\ Foundation Testing\TestCase的副本,但不扩展PHPUnit_Framework_TestCase)的扩展,当我们希望在测试结束时清理DB时,laravel可以使用这个扩展),这是PHPUnit_Extensions_Selenium2TestCase的扩展。为了更好地设置laravel函数,我还在SeleniumClearTestCase函数createApplication(在安装时调用,我从laral test/TestCase取这个函数)中安装了这个函数