74

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

4

8 回答 8

152

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

但是,重要的是要注意,具有未解决依赖关系的测试将不会被执行(这是可取的,因为这会很快引起对失败测试的注意)。因此,在使用依赖项时要密切注意。

于 2009-12-16T14:46:13.273 回答
59

也许您的测试中存在设计问题。

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

每个测试都需要实例化和销毁它运行所需的一切,这将是完美的方法,你不应该在测试之间共享对象和状态。

您能否更具体地说明为什么 N 次测试需要相同的对象?

于 2008-08-13T20:13:42.830 回答
27

正确的答案是测试的正确配置文件。我遇到了同样的问题,并通过创建具有必要测试文件顺序的测试套件来解决它:

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>
于 2017-07-26T10:09:27.937 回答
8

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

于 2008-08-23T22:30:48.447 回答
7

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

于 2009-12-29T10:22:31.247 回答
4

替代解决方案:在测试中使用 static(!) 函数来创建可重用元素。例如(我使用 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 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类(我在那里制作 screenshot() 和其他不错的功能)它是MigrationToSelenium2(从 gi​​thub,使用 seleniumIDE + ff 插件“Selenium IDE:PHP Formatters”在 Firefox 中移植记录的测试)的扩展,这是我的类的扩展LaravelTestCase(它是 Illuminate\Foundation\Testing\TestCase 的副本,但不扩展 PHPUnit_Framework_TestCase),它设置 laravel 以在我们想要在测试结束时清理 DB 时访问 eloquent)这是 PHPUnit_Extensions_Selenium2TestCase 的扩展。要设置 laravel eloquent,我还在 SeleniumClearTestCase 函数 createApplication 中(调用 at setUp,我从 laral test/TestCase 中获取此函数)

于 2016-06-17T10:31:05.743 回答
2

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

最初我有两种方法,a。testCreateResource 和 b. 测试销毁资源

一种。测试创建资源

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

湾。测试销毁资源

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

我认为这是一个坏主意,因为 testDestroyResource 依赖于 testCreateResource。更好的做法是

一种。测试创建资源

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

湾。测试销毁资源

<?php
$app->createResource('resource');
$app->destroyResource('resource');
$this->assertFalse($app->hasResource('resource'));
?>
于 2010-03-28T10:42:21.223 回答
1

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

查看此站点以获取有关如何以避免此类问题的方式对测试进行分解的大量想法/信息。

于 2008-08-14T15:39:32.847 回答